(旧存档) 2019年上海ICPC网络赛总结
B. Light bulbs
题意: 有一个布尔数组 $ A_{n}$ ,下标从 $0$ 开始,有 $m$ 个操作,每个操作要求对区间 $[l,r]$ 取反,操作完成,输出区间 $[0,n-1]$ 中 $1$ 的个数。
范围: 有多组数据 $T$ 。$ 1\le T \le 1000 ,1 \le n \le 10^{6},1\le m \le 1000 $
限制: 时限:$1000ms$,内存:$8192K$
解法: 那么小的内存显然不能使用线段树维护区间。由于操作次数 $m$ 小于1000, 可以离散化后统计区间中 1 的个数, 时间复杂度 $O(TMlogM)$ 。
代码一: 使用map
离散化(755 ms)
#include <cstdio>
#include <map>
using namespace std;
const int N = 1005;
int main() {
int t;
scanf("%d", &t);
map<int, int> s;
for (int k = 1; k <= t; ++k) {
int n, m;
s.clear();
scanf("%d%d", &n, &m);
for (int i = 0; i < m; ++i) {
int l, r;
scanf("%d%d", &l, &r);
s[l] ^= 1, s[r + 1] ^= 1;
}
int state = 0, ans = 0;
int last = 0;
for (auto& p: s) {
if(!p.second) continue;
if (state) ans += p.first - last;
else last = p.first;
state = !state;
}
printf("Case #%d: %d\n", k, ans);
}
return 0;
}
代码二: 数组离散化+bitset
(544 ms)
#include <cstdio>
#include <algorithm>
#include <bitset>
using namespace std;
const int N = 1005;
struct query {
int l, r;
} q[N];
int a[N << 1];
bitset<N << 1> b;
int main() {
int t;
scanf("%d", &t);
for (int k = 1; k <= t; ++k) {
int n, m, tot = 0;
b.reset();
scanf("%d%d", &n, &m);
for (int i = 0; i < m; ++i) {
scanf("%d%d", &q[i].l, &q[i].r), ++q[i].r;
a[tot++] = q[i].l, a[tot++] = q[i].r;
}
sort(a, a + tot);
tot = unique(a, a + tot) - a;
for (int i = 0; i < m; ++i) {
int l = lower_bound(a, a + tot, q[i].l) - a + 1;
int r = lower_bound(a, a + tot, q[i].r) - a + 1;
b[l].flip();
b[r].flip();
}
int j = 0, state = 0, ans = 0;
for (int i = 0; i <= tot; ++i) {
if (!b[i]) continue;
if (state) ans += a[i - 1] - a[j - 1];
else j = i;
state = !state;
}
printf("Case #%d: %d\n", k, ans);
}
return 0;
}
D. Counting Sequences I
题意: 有一个数组 $a_{n}$ $(n\ge2)$ ,下标从 $1$ 开始, 问你满足:$a_1+a_2+ …+a_n = a_1 \times a_2 \times … \times a_n$ 的数组 $a_n$ 的个数。
范围: 有多组数据 $T$ 。$1\le T \le 300 ,1 \le n \le 3000$
限制: 时限:$1000ms$,内存:$8192K$
解法一: 离线打表即可 (这个表,还是不是很好打滴),如果先不选择 $1$, 考虑选中了其他 $x$ 个数, 其乘积为 $mul$ , 和为 $sum$ , 那么添加 $mul - sum$ 个 $1$ 后,有 $a_1+a_2+ …+a_n = a_1 \times a_2 \times … \times a_n$ 。那么考虑搜索的算法,当 $mul - sum$ 已经大于可以使用的 $1$ 的数量时剪枝。(可行性剪枝)
打表代码:(数据生成用时 13s)
#include <cstdio>
#include <chrono>
using namespace std;
using namespace chrono;
typedef long long ll;
const int N = 3005;
const ll MOD = 1e9 + 7;
int cnt[N], n;
ll inv_fac[N], fac[N], ans;
ll quick_pow(ll a, ll p) {
ll res = 1;
while (p) {
if (p & 1) res = res * a % MOD;
a = a * a % MOD;
p >>= 1;
}
return res;
}
inline ll inv(int a) {
return quick_pow(a, MOD - 2);
}
void dfs(int now, int pos, ll sum, ll mul) {
int len = now + (mul - sum);
if (len > n) return;
if (len == n) {
ll res = fac[n];
for (int i = 2; i < N; ++i) {
if (cnt[i]) {
res = res * inv_fac[cnt[i]] % MOD;
}
}
res = res * inv_fac[mul - sum] % MOD;
ans = (ans + res) % MOD;
return;
}
for (int i = pos; i >= 2; --i) {
++cnt[i];
dfs(now + 1, i, sum + i, mul * i);
--cnt[i];
}
}
void init() {
freopen("C:\\output\\out.txt", "w", stdout);
fac[0] = 1, inv_fac[0] = inv(fac[0]);
for (int i = 1; i < N; ++i) {
fac[i] = fac[i - 1] * i % MOD;
inv_fac[i] = inv(fac[i]);
}
}
int main() {
auto t1 = steady_clock::now();
init();
for (n = 1; n < N; ++n) {
ans = 0;
dfs(0, n, 0, 1);
printf("%lld,", ans);
}
auto t2 = steady_clock::now();
printf("\n%fms\n", (t2 - t1).count() / 1e6);
return 0;
}
解法二: 离线查询 + 剪枝搜索。注意到上述打表代码对某个询问 $n$ 的dfs
过程中,实际上会出现其他的满足条件的解,而以上的暴力算法并没有统计这些解,在下一次遇到这些解还得重新计算。因此,我们可以统计所有在结果中出现的解,这样下次可以不必再次dfs计算,一次dfs便得到所有结果。
代码二: dfs 处理所有的值 (145 ms)
#include <cstdio>
using namespace std;
typedef long long ll;
const int N = 3005;
const ll MOD = 1e9 + 7;
ll inv_fac[N], fac[N], ans[N], cnt[N];
ll quick_pow(ll a, ll p) {
ll res = 1;
while (p) {
if (p & 1) res = res * a % MOD;
a = a * a % MOD;
p >>= 1;
}
return res;
}
inline ll inv(int a) {
return quick_pow(a, MOD - 2);
}
void dfs(int now, int pos, ll sum, ll mul) {
int len = now + (mul - sum);
if (len >= N) return;
if (len > 0) {
ll res = fac[len];
for (int i = 2; i < N; ++i) {
if (cnt[i]) {
res = res * inv_fac[cnt[i]] % MOD;
}
}
res = res * inv_fac[mul - sum] % MOD;
ans[len] = (ans[len] + res) % MOD;
}
for (int i = pos; i >= 2; --i) {
++cnt[i];
dfs(now + 1, i, sum + i, mul * i);
--cnt[i];
}
}
void init() {
fac[0] = 1, inv_fac[0] = inv(fac[0]);
for (int i = 1; i < N; ++i) {
fac[i] = fac[i - 1] * i % MOD;
inv_fac[i] = inv(fac[i]);
}
dfs(0, 3000, 0, 1);
}
int main() {
init();
int t, n;
scanf("%d",&t);
while(t--) {
scanf("%d",&n);
printf("%lld\n", ans[n]);
}
return 0;
}
解法三: 可以对解法二进行优化,只处理与询问有关的值。
代码三: dfs 处理与询问有关的值 (26 ms)
#include <cstdio>
using namespace std;
typedef long long ll;
const ll N = 3005, MOD = 1e9 + 7;
int cnt[N], query[N];
bool hasQuery[N];
ll p[N], inv_p[N], res[N];
ll quick_pow(ll a, ll p) {
ll res = 1;
while (p) {
if (p & 1) res = res * a % MOD;
a = a * a % MOD;
p >>= 1;
}
return res;
}
inline ll inv(ll a) {
return quick_pow(a, MOD - 2);
}
inline void process_query(int n, ll sum, ll mul) {
ll ans = p[n];
for (int i = 2; i < N; ++i) {
if (cnt[i]) ans = ans * inv_p[cnt[i]] % MOD;
}
ans = ans * inv_p[mul - sum] % MOD;
res[n] = (res[n] + ans) % MOD;
}
void dfs(int now, int pos, ll sum = 0, ll mul = 1) {
int n = now + (mul - sum);
if (n >= N) return;
if (n > 0 && hasQuery[n]) process_query(n, sum, mul);
for (int i = pos; i >= 2; --i) {
++cnt[i];
dfs(now + 1, i, sum + i, mul * i);
--cnt[i];
}
}
inline void init() {
p[0] = 1, inv_p[0] = inv(p[0]);
for (int i = 1; i < N; ++i) {
p[i] = i * p[i - 1] % MOD;
inv_p[i] = inv(p[i]);
}
}
int main() {
init();
int t, x;
scanf("%d", &t);
for (int i = 1; i <= t; ++i) {
scanf("%d", &x);
hasQuery[x] = 1, query[i] = x;
}
dfs(0, N);
for (int i = 1; i <= t; ++i) {
printf("%lld\n", res[query[i]]);
}
return 0;
}
J. Stone game
题意: 有一个多重数集S, 将S划分为两部分 $S_{1}, S_{2}$ 并且 $Sum(S_{1}) \ge Sum(S_{2})$ && $Sum(S_{1})-Min(S_{1})\le Sum(S_{2})$, 求划分数的个数。
范围: 有多组数据 $T$ 。$1\le T \le 10 ,1 \le n \le 300,1\le a_{i} \le 500$
限制: 时限:$3000ms$,内存:$262144K$
解法: 先将数组从大到小排序,然后进行$dp$。设 $dp[i][j]$ 表示以 $a[i]$ 为最小值的,和为 $j$ 的集合 $S$ 的个数,那么显然:$dp[i][j] += dp[i-1][j-a[i]]$ 。在 $dp$ 过程中更新答案,过程中 $dp$ 数组第一维可以省略。
代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <functional>
using namespace std;
typedef long long ll;
const ll MOD = 1e9 + 7;
int a[305];
ll dp[150005];
int main() {
int t;
scanf("%d", &t);
while (t--) {
memset(dp, 0, sizeof(dp));
int n = 0, sum = 0, ans = 0;
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
sum += a[i];
}
sort(a + 1, a + 1 + n, greater<int>());
dp[0] = 1;
for (int i = 1; i <= n; ++i) {
for (int j = (sum - 2 * a[i] + 1) / 2; 2 * j + a[i] <= sum; ++j) {
ans = (ans + dp[j]) % MOD;
}
for (int j = sum - a[i]; j >= 0; --j) {
dp[j + a[i]] = (dp[j + a[i]] + dp[j]) % MOD;
}
}
printf("%d\n", ans);
}
return 0;
}
L. Digit sum
题意: 求 $[1,n]$中,b进制的数位和
范围: 有多组数据 $T$ 。$1\le T \le 10^{5} ,1 \le n \le 10^{6},1\le b \le 10$
限制: 时限:$2000ms$,内存:$131072K$
解法一: 没看清数据范围,用数位 dp
演了一发,实际上暴力就好 $qaq$。
代码一:
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
const int N = 32, BASE = 15;
ll dp[N][2][10][BASE];
int a[N];
ll dfs(int pos, int state, bool lead, bool limit, int x, int base) {
if (pos == -1) return state;
int up = limit ? a[pos] : base - 1;
if (!lead && !limit && dp[pos][state][x][base] != -1) return dp[pos][state][x][base];
long ans = 0;
for (int i = 0; i <= up; ++i) {
if (state == 0) {
if (lead && i == 0) {
ans += dfs(pos - 1, 0, true, limit && i == up, x, base);
} else if (i == x) {
ans += dfs(pos - 1, 1, false, limit && i == up, x, base) +
dfs(pos - 1, 0, false, limit && i == up, x, base);
} else {
ans += dfs(pos - 1, 0, false, limit && i == up, x, base);
}
} else {
ans += dfs(pos - 1, 1, lead && i == 0, limit && i == up, x, base);
}
}
if (!lead && !limit) return dp[pos][state][x][base] = ans;
return ans;
}
ll solve(ll n, int x, int base) {
int tot = 0;
while (n != 0) {
a[tot++] = (int) (n % base);
n /= base;
}
return dfs(tot - 1, 0, true, true, x, base);
}
int main() {
int t;
scanf("%d", &t);
memset(dp, -1, sizeof(dp));
for (int k = 1; k <= t; ++k) {
int base, n;
scanf("%d%d", &n, &base);
ll sum = 0;
for (int i = 0; i < base; ++i) {
sum += solve(n, i, base) * i;
}
printf("Case #%d: %lld\n", k, sum);
}
return 0;
}
解法二: 暴力… …
代码二:
#include <cstdio>
typedef long long ll;
const int N = 1e6 + 5;
ll ans[N][15];
int resolve(int x, int base) {
int ans = 0;
while (x) {
ans += x % base;
x /= base;
}
return ans;
}
void init() {
for (int i = 1; i <= 1e6; ++i) {
for (int base = 2; base <= 10; ++base) {
ans[i][base] += ans[i - 1][base] + resolve(i, base);
}
}
}
int main() {
init();
int t;
scanf("%d", &t);
for (int i = 1; i <= t; ++i) {
int n, base;
scanf("%d%d", &n, &base);
printf("Case #%d: %lld\n", i, ans[n][base]);
}
return 0;
}