【题录】ICPC2020 南京站、济南站
南京站:
J.
操作:区间取max;查询:区间的石堆 + 一堆给定数目 (x) 的石堆所构成的 nim 游戏使先手必胜的先手操作个数(先手第一步的操作方案数)。
由组合游戏的结论有若区间石子的异或和 ^ x 不为 0 则先手必胜,令这个总的异或和为 S。考虑一个石堆,如果先手最开始拿走这个石堆内的石子能够使得异或和 S' 为 0,已知只有一种拿走的操作方式。所以问题只需要考虑一开始动第 k 堆石子能否先手必胜,若能则答案 + 1。除去第 k 堆石子的其余石堆的石子数异或和为 S ^ a[k], 当且仅当 S ^ a[k] < a[k] 的时候 a[k] 可以经过操作变成 S ^ a[k],而当且仅当在 S 的二进制表示中最高位的 1 的位置上,a[k] 也为 1 该条件成立。
因此使用吉司机线段树维护区间取 max 的操作,维护区间异或和以及每一个二进制位上为 1 的区间内数的个数。
#include <bits/stdc++.h> using namespace std; #define BITS 32 #define maxn 1000000 #define INF 1500000000 int n, q, a[maxn], minx[maxn], sminx[maxn], minnum[maxn], num[maxn][BITS]; int mark[maxn], sum[maxn], bits[BITS]; int read() { int x = 0, k = 1; char c; c = getchar(); while(c < '0' || c > '9') { if(c == '-') k = -1; c = getchar(); } while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); return x * k; } void Push_Up(int p, int l, int r) { int ls = p << 1, rs = p << 1 | 1; if(minx[ls] == minx[rs]) { minx[p] = minx[ls], minnum[p] = minnum[ls] + minnum[rs]; sminx[p] = min(sminx[ls], sminx[rs]); } else { if(minx[rs] < minx[ls]) swap(ls, rs); minx[p] = minx[ls], minnum[p] = minnum[ls]; sminx[p] = min(sminx[ls], minx[rs]); } for(int K = BITS - 1; K >= 0; K --) num[p][K] = num[ls][K] + num[rs][K]; sum[p] = sum[ls] ^ sum[rs]; } void Work(int p, int l, int r, int x) { if(minx[p] >= x) return; for(int K = BITS - 1; K >= 0; K --) { if(minx[p] & bits[K]) num[p][K] -= minnum[p]; if(x & bits[K]) num[p][K] += minnum[p]; } if(minnum[p] & 1) sum[p] ^= minx[p] ^ x; minx[p] = x; mark[p] = x; return; } void Push_Down(int p, int l, int r) { if(mark[p] == -INF) return; int mid = (l + r) >> 1; Work(p << 1, l, mid, mark[p]); Work(p << 1 | 1, mid + 1, r, mark[p]); mark[p] = -INF; } void Build(int p, int l, int r) { mark[p] = -INF; if(l == r) { minx[p] = sum[p] = a[l]; sminx[p] = INF; minnum[p] = 1; for(int K = BITS - 1; K >= 0; K --) if(a[l] & bits[K]) num[p][K] ++; return; } int mid = (l + r) >> 1; Build(p << 1, l, mid), Build(p << 1 | 1, mid + 1, r); Push_Up(p, l, r); } void Modify(int p, int l, int r, int x) { if(minx[p] >= x) return; if(minx[p] < x && sminx[p] > x) { Work(p, l, r, x); return; } int mid = (l + r) >> 1; Push_Down(p, l, r); Modify(p << 1, l, mid, x), Modify(p << 1 | 1, mid + 1, r, x); Push_Up(p, l, r); } void Update(int p, int l, int r, int L, int R, int x) { if(l > R || r < L) return; if(L <= l && R >= r) { Modify(p, l, r, x); return; } int mid = (l + r) >> 1; Push_Down(p, l, r); Update(p << 1, l, mid, L, R, x); Update(p << 1 | 1, mid + 1, r, L, R, x); Push_Up(p, l, r); } int Query(int p, int l, int r, int L, int R) { if(l > R || r < L) return 0; if(l >= L && r <= R) return sum[p]; Push_Down(p, l, r); int mid = (l + r) >> 1; return Query(p << 1, l, mid, L, R) ^ Query(p << 1 | 1, mid + 1, r, L, R); } int Query2(int p, int l, int r, int L, int R, int x) { if(l > R || r < L) return 0; if(l >= L && r <= R) return num[p][x]; Push_Down(p, l, r); int mid = (l + r) >> 1; return Query2(p << 1, l, mid, L, R, x) + Query2(p << 1 | 1, mid + 1, r, L, R, x); } int main() { n = read(), q = read(); bits[0] = 1; for(int i = 1; i <= n; i ++) a[i] = read(); for(int i = 1; i < BITS; i ++) bits[i] = bits[i - 1] << 1; Build(1, 1, n); for(int i = 1; i <= q; i ++) { int op = read(), l = read(), r = read(), x = read(); if(op == 1) Update(1, 1, n, l, r, x); else { int S = Query(1, 1, n, l, r) ^ x; if(!S) printf("0\n"); else { int cnt = -1; while(S) S >>= 1, cnt ++; printf("%d\n", Query2(1, 1, n, l, r, cnt) + (bool) (x & bits[cnt])); } } } return 0; }
M.
简单树形dp, 状态设置 f[i][j][k(0\1)] 代表dp到 i 号节点,子树内已经选择了 j 个节点,当前节点选/不选的情况下干掉整个子树内的怪物所需要的最少能量值。
#include <bits/stdc++.h> using namespace std; #define maxn 2005 #define INF 10000000000000 #define LL long long int n, size[maxn], val[maxn]; LL g[maxn][maxn][2], f[maxn][maxn][2]; int read() { int x = 0, k = 1; char c; c = getchar(); while(c < '0' || c > '9') { if(c == '-') k = -1; c = getchar(); } while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); return x * k; } struct edge { int cnp = 1, head[maxn], last[maxn], to[maxn]; void add(int u, int v) { to[cnp] = v, last[cnp] = head[u], head[u] = cnp ++; } }E; void Down(LL &x, LL y) { if(y < x) x = y; } void DP(int u) { size[u] = 1; f[u][0][0] = val[u]; f[u][1][1] = 0; for(int i = E.head[u]; i; i = E.last[i]) { int v = E.to[i]; DP(v); for(int t1 = 0; t1 <= size[v]; t1 ++) for(int t2 = 0; t2 <= size[u]; t2 ++) { //down did not choose; Down(g[u][t1 + t2][0], f[v][t1][0] + f[u][t2][0] + val[v]); Down(g[u][t1 + t2][1], f[v][t1][0] + f[u][t2][1]); //down did choose Down(g[u][t1 + t2][0], f[v][t1][1] + f[u][t2][0]); Down(g[u][t1 + t2][1], f[v][t1][1] + f[u][t2][1]); } size[u] += size[v]; for(int t = 0; t <= size[u]; t ++) { f[u][t][0] = g[u][t][0], f[u][t][1] = g[u][t][1]; g[u][t][0] = g[u][t][1] = INF; } } } int main() { int T = read(); while(T --) { n = read(); E.cnp = 1; for(int i = 1; i <= n; i ++) E.head[i] = 0; for(int i = 2; i <= n; i ++) { int x = read(); E.add(x, i); } for(int i = 0; i <= n; i ++) for(int j = 0; j <= n; j ++) for(int t = 0; t <= 1; t ++) f[i][j][t] = g[i][j][t] = INF; for(int i = 1; i <= n; i ++) val[i] = read(); DP(1); for(int i = 0; i < n; i ++) printf("%lld ", min(f[1][i][0], f[1][i][1])); printf("%lld\n", min(f[1][n][0], f[1][n][1])); } return 0; }
济南站:
A.
列与列之间相互独立,可以拆开来做。每一列都是 n 个 n 元异或方程,求解自由元的个数可以用线性基 + bitset 优化。
H.
令 \(a_{i}\) 为最后一次选择了 \(i\) 路径上的点的时间,\(S\) 为 a_{i} 构成的集合。则此时的次数即为 \(max(S)\)。由 min-max 容斥有 \(max(S) = \sum_{T\subseteq S}^{}(-1)^{|T| - 1}min(T) \),该式对期望也成立,即 \(E(max(S)) = \sum_{T\subseteq S}^{}(-1)^{|T| - 1} E(min(T)) \) 。
而对于给定的路径集合,E(min(T)) 很容易计算。设一共覆盖的有 m 个点,期望的次数即为 n / m 次。
考虑转枚举集合 T 为枚举 m, 使用 dp 计算方案。树形 dp 状态: f[i][j][k][s(0\1)] 代表 dp 到 i 点,子树内覆盖了有 j 个点,已选择的路径向上覆盖了 k 个点,一共选择的路径条数为奇数 / 偶数。我的代码里面把完全覆盖的路径删掉了,但其实可以不用吧……?
#include <bits/stdc++.h> using namespace std; #define mod 998244353 #define maxn 350 #define maxm 100000 int n, m, f[maxn][maxn][maxn][2], size[maxn], dep[maxn], len[maxn], R[maxn][maxn]; int recfa[maxn], g[maxn][maxn][2]; bool mark[maxn][maxn]; int read() { int x = 0, k = 1; char c; c = getchar(); while(c < '0' || c > '9') { if(c == '-') k = -1; c = getchar(); } while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); return x * k; } struct node { int a, b; }e[maxn]; struct edge { int cnp = 1, head[maxm], last[maxm], to[maxm], id[maxm]; void add(int u, int v) { to[cnp] = v, last[cnp] = head[u], head[u] = cnp ++; } }E, E2; int add(int x, int y) { x += y; if(x >= mod) x -= mod; return x; } int sub(int x, int y) { x -= y; if(x < 0) x += mod; return x; } void Up(int &x, int y) { x += y; if(x >= mod) x -= mod; } int mul(int x, int y) { return 1ll * x * y % mod; } int Qpow(int x, int timer) { int base = 1; for(; timer; timer >>= 1, x = mul(x, x)) if(timer & 1) base = mul(base, x); return base; } bool Check(int u, int gra, int id) { if(gra != u) { for(int i = E2.head[u]; i; i = E2.last[i]) { int v = E2.to[i]; if(dep[v] < dep[e[id].a]) return 0; } } for(int i = E.head[u]; i; i = E.last[i]) { int v = E.to[i]; if(!Check(v, gra, id)) return 0; } return 1; } void dfs(int u, int fa) { dep[u] = dep[fa] + 1; recfa[u] = fa; for(int i = E.head[u]; i; i = E.last[i]) { int v = E.to[i]; dfs(v, u); } } void DP(int u, int fa) { f[u][0][0][0] = 1; for(int i = 1; i <= R[u][0]; i ++) { len[u] = max(len[u], dep[u] - dep[R[u][i]] + 1); f[u][1][dep[u] - dep[R[u][i]] + 1][1] ++; } if(len[u] >= 1) size[u] = 1; for(int i = E.head[u]; i; i = E.last[i]) { int v = E.to[i]; DP(v, u); for(int s1 = 0; s1 <= size[u]; s1 ++) for(int l1 = 0; l1 <= len[u]; l1 ++) for(int t1 = 0; t1 <= 1; t1 ++) for(int s2 = 0; s2 <= size[v]; s2 ++) for(int l2 = 0; l2 <= len[v]; l2 ++) for(int t2 = 0; t2 <= 1; t2 ++) { int s = s1 + s2; if((!l1) && (l2 >= 2)) s ++; int l = max(l1, l2 - 1); int t = t1 ^ t2; Up(g[s][l][t], mul(f[u][s1][l1][t1], f[v][s2][l2][t2])); } if(!len[u] && ((len[v] - 1) >= 1)) size[u] ++; size[u] += size[v]; len[u] = max(len[u], len[v] - 1); for(int s = 0; s <= size[u]; s ++) for(int l = 0; l <= len[u]; l ++) for(int t = 0; t <= 1; t ++) f[u][s][l][t] = g[s][l][t], g[s][l][t] = 0; } } int main() { n = read(), m = read(); for(int i = 2; i <= n; i ++) { int x = read(); recfa[i] = x; E.add(x, i); } dfs(1, 0); for(int i = 1; i <= m; i ++) { int a = read(), b = read(); e[i].a = a, e[i].b = b; if(mark[a][b]) continue; mark[a][b] = 1; E2.add(b, a); } for(int i = 1; i <= n; i ++) for(int j = 1; j <= n; j ++) mark[i][j] = 0; for(int i = 1; i <= m; i ++) { int a = e[i].a, b = e[i].b; if(mark[a][b]) continue; mark[a][b] = 1; bool flag = true; int c = b; while(c != recfa[a]) { for(int j = E2.head[c]; j; j = E2.last[j]) { if(c == b && E2.to[j] == a) continue; if(dep[E2.to[j]] >= dep[a]) flag = false; } c = recfa[c]; } if(flag) R[b][++ R[b][0]] = a; } DP(1, 0); int ans = 0; for(int i = 1; i <= n; i ++) { int even = add(f[1][i][0][0], f[1][i][1][0]); int odd = add(f[1][i][0][1], f[1][i][1][1]); Up(ans, mul(Qpow(i, mod - 2), sub(odd, even))); } ans = mul(ans, n); printf("%d\n", ans); return 0; }
J.
官方题解很清晰啦,树是二分图,用两位保证同边的点之间没有连边,不同边的点之间用锁和钥匙的解法。
#include <bits/stdc++.h> using namespace std; #define maxn 200000 #define int long long int n, mark[maxn], a[maxn], rec[maxn], bits[maxn]; int read() { int x = 0, k = 1; char c; c = getchar(); while(c < '0' || c > '9') { if(c == '-') k = -1; c = getchar(); } while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); return x * k; } struct edge { int cnp = 1, to[maxn], last[maxn], head[maxn]; void add(int u, int v) { to[cnp] = v, last[cnp] = head[u], head[u] = cnp ++; to[cnp] = u, last[cnp] = head[v], head[v] = cnp ++; } }E; void dfs(int u, int fa) { mark[u] = mark[fa] ^ 1; for(int i = E.head[u]; i; i = E.last[i]) { int v = E.to[i]; if(v == fa) continue; dfs(v, u); } } signed main() { n = read(); bits[0] = 1; for(int i = 1; i < 61; i ++) bits[i] = bits[i - 1] << 1; for(int i = 1; i < n; i ++) { int x = read(), y = read(); E.add(x, y); } dfs(1, 0); int cnt = 0; for(int i = 1; i <= n; i ++) if(!mark[i]) rec[i] = ++ cnt; if(cnt > n / 2) { cnt = 0; for(int i = 1; i <= n; i ++) mark[i] ^= 1; for(int i = 1; i <= n; i ++) if(!mark[i]) rec[i] = ++ cnt; else rec[i] = 0; } for(int i = 1; i <= n; i ++) { for(int j = 1; j <= 60; j ++) a[j] = 0; for(int j = cnt + 3; j <= 60; j ++) a[j] = 1; if(!mark[i]) { for(int j = 1; j <= cnt; j ++) a[j] = (j == rec[i]) ? 0 : 1; a[cnt + 1] = 1; } else { for(int j = E.head[i]; j; j = E.last[j]) { int v = E.to[j]; a[rec[v]] = 1; } a[cnt + 2] = 1; } int ans = 0; for(int j = 1; j <= 60; j ++) if(a[j]) ans += bits[j - 1]; if(i != n) printf("%lld ", ans); else printf("%lld\n", ans); } return 0; }
K.
考虑把 a 由高位到低位建到一棵 trie 树上面去。那么一个 S 在第 i 位上面为 1 等价于把第 i + 1 位的两棵子树翻转一次。可以用 f[u][K] 表示 u 的子树里面随便翻转后求第 K 小的最小值。这个可以用 dp 去求。(由于树高为 log 的级别,所以最多 n * log 个状态)。如果没有 S 的上下界限制,易知答案即为 f[1][K]。如果有上下界,则从高位到低位所选定的 S 一定经历几个过程:和 L,R 都相同;和 L, R 中的一个相同;由于不卡位后面没有限制。因为只有 log 位,所以可以暴力枚举都相同的位,剩下的在trie 树上面 dp,最后取最小值。
#include <bits/stdc++.h> using namespace std; #define maxn 3000005 #define INF 1200000000 #define BITS 31 int T[maxn][2], bits[maxn], tot = 1, size[maxn], n, Q, L, R, K, a[maxn]; int lim; vector<int> f[maxn]; int read() { int x = 0, k = 1; char c; c = getchar(); while(c < '0' || c > '9') { if(c == '-') k = -1; c = getchar(); } while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); return x * k; } void Ins(int x) { int now = 1; for(int i = BITS - 1; i >= 0; i --) { int t = (bool) (x & bits[i]); if(!T[now][t]) T[now][t] = ++ tot; now = T[now][t]; } size[now] ++; } void DP(int u, int dig) { if(!u) { f[u].resize(1, 0); return; } int ch0 = T[u][0], ch1 = T[u][1]; if(!ch0 && !ch1) { f[u].resize(size[u] + 1, 0); return; } DP(ch0, dig - 1), DP(ch1, dig - 1); size[u] += size[ch0], size[u] += size[ch1]; f[u].resize(size[u] + 1); for(int i = 1; i <= size[u]; i ++) { // choose to be 0 for the next digit if(size[ch0] >= i) f[u][i] = f[ch0][i]; else f[u][i] = f[ch1][i - size[ch0]] + bits[dig - 1]; //choose to be 1 if(size[ch1] >= i) f[u][i] = min(f[u][i], f[ch1][i]); else f[u][i] = min(f[u][i], f[ch0][i - size[ch1]] + bits[dig - 1]); } } // lower bound int Work(int u, int dig, int K, int opt) { if(dig == -1) { if(K <= size[u]) return 0; else return INF; } int ans = INF; bool t = lim & (bits[dig]); int opt1 = opt; if(opt < 0) opt1 = -opt; if(size[T[u][t]] >= K) ans = min(ans, Work(T[u][t], dig - 1, K, opt1)); else ans = min(ans, Work(T[u][t ^ 1], dig - 1, K - size[T[u][t]], opt1) + bits[dig]); if(((opt == 1) && !t) || ((opt == 2) && t)) { t ^= 1; if(size[T[u][t]] >= K) ans = min(ans, f[T[u][t]][K]); else ans = min(ans, f[T[u][t ^ 1]][K - size[T[u][t]]] + bits[dig]); } return ans; } int main() { n = read(), Q = read(); bits[0] = 1; for(int i = 1; i <= BITS; i ++) bits[i] = bits[i - 1] << 1; for(int i = 1; i <= n; i ++) { a[i] = read(); Ins(a[i]); } DP(1, BITS); for(int i = 1; i <= Q; i ++) { L = read(), R = read(), K = read(); int now = 1, x = 0; int ans = INF; for(int j = BITS - 1; j >= 0; j --) { bool t = L & bits[j]; if((L & bits[j]) == (R & bits[j])) { if(size[T[now][t]] >= K) now = T[now][t]; else K -= size[T[now][t]], now = T[now][t ^ 1], x += bits[j]; } if((L & bits[j]) != (R & bits[j])) { lim = L, ans = min(ans, x + Work(now, j, K, -1)); lim = R, ans = min(ans, x + Work(now, j, K, -2)); break; } else if(j == 0) ans = x; } printf("%d\n", ans); } return 0; }
L.
数位dp。因为最多加100,所以在第 7 位至多进位一次。状态 f[x][y][s][t] 代表 dp 到第 x 位,连续的 1 的奇偶性,一共的 1 的奇偶性,是否卡位。最后的 6 位暴力枚举判断是否满足条件。
#include <bits/stdc++.h> using namespace std; #define int long long #define maxn 1000 #define BITS 62 int m, L, ans, a[maxn], cnt[maxn], bits[maxn], f[maxn][2][2][2]; void Up(int &x, int y) { x += y; } // x : digit number; y : consequent 1s'; s : tot 1s' parity; t : upper limitation; void trans(int x, int y, int s, int t) { int x1 = x - 1; bool num1 = L & bits[x1]; if(!t || (t && num1)) { int y1 = y ^ 1, s1 = s ^ 1, t1 = t; Up(f[x1][y1][s1][t1], f[x][y][s][t]); } int y1 = 0, s1 = s, t1 = t; if(t && (num1)) t1 = 0; Up(f[x1][y1][s1][t1], f[x][y][s][t]); } void DP() { for(int x = BITS - 1; x >= 7; x --) for(int y = 0; y <= 1; y ++) for(int s = 0; s <= 1; s ++) for(int t = 0; t <= 1; t ++) f[x][y][s][t] = 0; f[BITS - 1][0][0][1] = 1; for(int x = BITS - 1; x >= 8; x --) for(int y = 0; y <= 1; y ++) for(int s = 0; s <= 1; s ++) for(int t = 0; t <= 1; t ++) if(f[x][y][s][t]) trans(x, y, s, t); } void Cal() { int lim = L & (bits[7] - 1); ans = 0; for(int y = 0; y <= 1; y ++) { for(int s = 0; s <= 1; s ++) for(int t = 0; t <= 1; t ++) { int num1 = f[7][y][s][t]; if(!num1) continue; for(int i = 0; i < bits[7]; i ++) { if(t && (i > lim)) continue; bool flag = true; int num = i; int s1 = s; for(int j = 0; j < m; j ++, num ++) { if(num >= bits[7]) { if(!y) s1 ^= 1; num = 0; } if((s1 ^ (cnt[num] & 1)) != a[j]) { flag = false; break; } } if(flag) ans += num1; } } } } signed main() { ios::sync_with_stdio(0); cin.tie(0); int T; cin >> T; bits[0] = 1; for(int i = 1; i < BITS; i ++) bits[i] = bits[i - 1] << 1; for(int i = 0; i < bits[7]; i ++) { int x = i; while(x) { if(x & 1) cnt[i] ++; x >>= 1; } } while(T --) { cin >> m >> L; for(int i = 0; i < m; i ++) cin >> a[i]; DP(); Cal(); cout << ans << endl; } return 0; }