20240625
T1
洛谷 P3267 侦查守卫
考虑对一个子树,我们关心什么。我们关心的是其中最深的未覆盖关键点的深度以及最浅守卫的深度。如果把这两个都扔到状态里,空间爆炸。发现如果一个子树还有关键点没有覆盖,那关心其中最浅守卫的深度就是无意义的,因为反正还要再放一个,而且新放的一定比子树中的浅。所以只需要在子树合法时记录最浅守卫深度,不合法时记录最深未覆盖点深度即可。设 \(f[i][j]\) 表示 \(i\) 子树合法的情况下向上最多还能盖 \(j\) 条边,\(g[i][j]\) 表示 \(i\) 子树最深的未覆盖点到 \(i\) 的边数 \(+1\),两种情况的最小代价。转移就每次加入一棵子树。注意一轮更新结束后 \(f\) 要做后缀最小值,\(g\) 要做前缀最小值,因为如果更优的状态有着更优的代价,那当前这个状态就可以直接把那个更优的代价拿过来。
代码
#include <iostream>
#include <string.h>
using namespace std;
bool bg;
const int inf = 0x3f3f3f3f;
int n, m, d;
int w[500005];
bool key[500005];
int head[500005], nxt[1000005], to[1000005], ecnt;
void add(int u, int v) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt; }
int f[500005][25];
int g[500005][25];
void dfs(int x, int fa) {
if (key[x])
f[x][0] = g[x][0] = w[x];
for (int i = 1; i <= d; i++) f[x][i] = w[x];
f[x][d + 1] = inf;
for (int i = head[x]; i; i = nxt[i]) {
int v = to[i];
if (v != fa) {
dfs(v, x);
for (int j = d; ~j; j--) {
f[x][j] = min(f[x][j] + g[v][j], g[x][j + 1] + f[v][j + 1]);
f[x][j] = min(f[x][j], f[x][j + 1]);
}
g[x][0] = f[x][0];
for (int j = 1; j <= d + 1; j++) {
g[x][j] += g[v][j - 1];
g[x][j] = min(g[x][j], g[x][j - 1]);
}
}
}
}
bool ed;
int main() {
cerr << (&ed - &bg) / 1024.0 / 1024.0 << "\n";
freopen("observer.in", "r", stdin);
freopen("observer.out", "w", stdout);
cin >> n >> d;
for (int i = 1; i <= n; i++) cin >> w[i];
cin >> m;
for (int i = 1, x; i <= m; i++) cin >> x, key[x] = 1;
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
add(u, v);
add(v, u);
}
dfs(1, 0);
cout << f[1][0] << "\n";
return 0;
}
T2
NFLSOJ P5022 Imbalance
考虑没有限制时怎么做。显然可以异或前缀和,然后 01 trie 维护。这应当是平凡的。
接下来考虑限制。容易想到对每种值前缀和,然后两种数 \(a, b\) 在 \([l, r]\) 出现次数相同当且仅当 \(S_b[r] - S_b[l - 1] = S_a[r] - S_a[l - 1]\)。移项,得 \(S_b[r] - S_a[r] = S_b[l - 1] - S_a[l - 1]\)。这样两边都只分别与 \(i, j\) 有关。对于每个 \(i\),我们算出 \(S_a[i] - S_b[i], S_b[i] - S_c[i], S_a[i] - S_c[i]\) 三个值。这样区间 \([l, r]\) 符合条件当且仅当 \(l - 1\) 的三元组与 \(r\) 的三元组有至少一个数相等。
注意到“至少一个”,我们可以想到容斥。初步地,我们将每一个三元组 \(\{ a, b, c \}\) 拆成 \(\{ a, b, c \}, \{ a, b, -1 \}, \{ a, -1, b \}\) 等等这样 \(8\) 个三元组,对每个三元组建立一颗 01 trie,每次插入时把插入的数分别插入这 \(8\) 个对应的 trie 中,查询时我们使用容斥,把查询的位置的三元组对应的 \(8\) 个 trie 的根同时扔到 trie 里查。这样时空复杂度单 \(\log\),只是都有 \(8\) 倍常数。
过不去,考虑优化。能够发现若两个这样的三元组中有两个数相同,这两个三元组必然全等(结合第一段讲的判断条件理解)。于是可以发现查询时有非常多冗余,比如说 \(\{ a, b, -1 \}\) 与 \(\{ a, -1, c \}\) 与 \(\{ -1, b, c \}\) 与 \(\{ a, b, c \}\) 的 trie 必然全等。于是可以省去“强制两个数相等”情况的两种插入与“三种全部相等”的插入,查询时也相应简化。这样就只有 \(5\) 倍常数了,可以通过。
代码
#include <iostream>
#include <map>
using namespace std;
const int inf = 2147483647;
inline char nnc(){
static char buf[1000005],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000005,stdin),p1==p2)?EOF:*p1++;
}
inline int read() {
int ret = 0;
char c = nnc();
while (!isdigit(c) && !isalpha(c)) c = nnc();
while (isdigit(c) || isalpha(c)) ret = ret * 10 + c - 48, c = nnc();
return ret;
}
bool bg;
int n, opt;
struct node {
int a, b, c;
};
bool operator<(node a, node b) { return (a.a == b.a ? (a.b == b.b ? (a.c < b.c) : (a.b < b.b)) : (a.a < b.a)); }
int ncnt = 1;
struct Trie {
const int MX = 29;
int T[46000005][2];
int cnt[46000005];
void Insert(int rt, int x) {
int p = rt;
for (int i = MX; ~i; i--) {
int t = (x >> i) & 1;
T[p][t] ? 0 : (T[p][t] = ++ncnt);
p = T[p][t];
cnt[p]++;
}
}
int Query(int a, int b, int c, int d, int e, int x) {
int ret = 0;
for (int i = MX; ~i; i--) {
int t = !((x >> i) & 1);
if (cnt[T[a][t]] - cnt[T[b][t]] - cnt[T[c][t]] - cnt[T[d][t]] + cnt[T[e][t]] * 2) {
ret |= (1 << i);
a = T[a][t], b = T[b][t], c = T[c][t], d = T[d][t], e = T[e][t];
} else
a = T[a][!t], b = T[b][!t], c = T[c][!t], d = T[d][!t], e = T[e][!t];
}
return ret;
}
} Trie;
map<node, int> rt;
int p[300005], A[300005];
int mp(node a) {
if (!rt.count(a))
return rt[a] = ++ncnt;
else
return rt[a];
}
void Add(node x, int y) {
node a;
int t = 1;
Trie.Insert(t, y);
a = (node) { x.a, inf, inf };
Trie.Insert(mp(a), y);
a = (node) { inf, x.b, inf };
Trie.Insert(mp(a), y);
a = (node) { inf, inf, x.c };
Trie.Insert(mp(a), y);
a = (node) { x.a, x.b, inf };
Trie.Insert(mp(a), y);
}
int Query(node x, int y) {
int b, c, d, e;
b = mp((node) { x.a, inf, inf });
c = mp((node) { inf, x.b, inf });
d = mp((node) { inf, inf, x.c });
e = mp((node) { x.a, x.b, inf });
return Trie.Query(1, b, c, d, e, y);
}
int pre[300005];
bool ed;
signed main() {
// cerr << (&ed - &bg) / 1024.0 / 1024.0 << "\n";
freopen("imbalance.in", "r", stdin);
freopen("imbalance.out", "w", stdout);
n = read(), opt = read();
for (int i = 1; i <= n; i++) p[i] = read();
for (int i = 1; i <= n; i++) A[i] = read();
int lans = 0;
Add((node) { 0, 0, 0 }, 0);
int a, b, c;
a = b = c = 0;
for (int i = 1; i <= n; i++) {
if (opt) {
p[i] = (p[i] ^ lans) % 3;
A[i] ^= lans;
}
pre[i] = pre[i - 1] ^ A[i];
p[i] == 0 ? (++a) : (p[i] == 1 ? ++b : ++c);
int x = a - b, y = a - c, z = b - c;
cout << (lans = Query((node) { x, y, z }, pre[i])) << " ";
Add((node) { x, y, z }, pre[i]);
}
cout << "\n";
return 0;
}
T3
摆。
根据性质缩减冗余状态。

浙公网安备 33010602011771号