[The 2020 ICPC Asia Macau Regional Contest] I. Nim Cheater
题目大意
开始是一个空的多重集,然后两种操作:
- ADD a b: 加入一个数值为a,权值为b的元素。
- DEL: 删除最近一次ADD操作
然后,每次操作过后输出要让这些a异或起来为0,要删除哪些a,且要使得b的和最小。
题解
如果没有题目的8M空间限制,那么我们有dp方程
\[f[i][j] = min\{f[i-1][j], f[i-1][j\oplus a[i]]+b[i]\}
\]
其中,\(f[i][j]\)表示前i个数取一些出来异或和为j的最小值。如果遇到DEL操作,我们就回滚版本就行了,就是把i往上退一格。
但是,有空间的限制显然是不行的,8M大约只能开2000000个int。
因为删除就是一个回溯过程,类似dfs,考虑建树。当加入一个数,就新建一个节点,然后删除就相当于回溯,这样一棵树就出来了。然后我们每次最多就会保存一条链。但是我们会发现依然没有优化多少,因为一条链的情况就会卡掉。再考虑轻重树链剖分,一条链上最多有\(\log n\)条轻边。然后我们想,如果是轻边,我就复制一份dp数组,如果是重边,直接滚动数组或者说覆盖。这也就是说一条链上最多会有\(\log n * 16384\)个int,非常满足题目条件。
代码
//#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
using namespace std;
#define X first
#define Y second
#define PB push_back
#define SZ(x) (int)x.size()
#define MK make_pair
#define ALL(x) x.begin(), x.end()
#define lc (o<<1)
#define rc ((o<<1)|1)
void debug() {
cerr << endl;
}
template<typename Head, typename... Tail>
void debug(Head H, Tail... T) {
cerr << " " << H;
debug(T...);
}
#define dbg(...) cerr << "[" << #__VA_ARGS__ << "]:", debug(__VA_ARGS__)
#define endl '\n'
using LL = long long;
const int _ = 5, maxn = 40000;
int son[maxn+_], now, a[maxn+_], b[maxn+_], fa[maxn+_], n, tot, w[maxn+_];
int first[maxn+_], tote = 0, ans[maxn+_], head[maxn+_];
pair<int, int> id[maxn+_];
int dp[800005];
struct Edge {
int y, next;
Edge(int y=0, int next=-1): y(y), next(next) {}
}edges[maxn+_];
void addedge(int x, int y) {
edges[tote] = Edge(y, first[x]);
first[x] = tote++;
}
void dfs(int x) {
son[x] = 1;
int maxt = 0;
w[x] = -1;
for (int i = first[x]; i != -1; i = edges[i].next) {
dfs(edges[i].y);
son[x] += son[edges[i].y];
if (maxt < son[edges[i].y]) maxt = son[edges[i].y], w[x] = edges[i].y;
}
}
const int M = 1<<14;
void dfsdp(int x, int last, int st, int xorsum) {
if (x) {
if (last == st) {
for (int i = 0; i < M; ++i)
dp[st+M+i] = min(dp[st+i], dp[st+(i^a[x])] + b[x]);
for (int i = 0; i < M; ++i)
dp[st+i] = dp[st+M+i];
}
else {
for (int i = 0; i < M; ++i)
dp[st+i] = min(dp[last+i], dp[last+(i^a[x])] + b[x]);
}
}
for (int i = head[x]; i != -1; i = id[i].Y)
ans[id[i].X] = dp[st+xorsum];
for (int i = first[x]; i != -1; i = edges[i].next)
if (edges[i].y != w[x]) dfsdp(edges[i].y, st, st + M, xorsum^a[edges[i].y]);
if (w[x] > -1) dfsdp(w[x], st, st, xorsum^a[w[x]]);
}
int main() {
int T = 1;
for (int kase = 1; kase <= T; ++kase) {
scanf("%d", &n);
tot = 0;
memset(fa, 0, sizeof(fa));
memset(first, -1, sizeof(first));
memset(head, -1, sizeof(head));
memset(id, -1, sizeof(id));
int t = 0;
now = 0;
for (int i = 0; i < n; ++i) {
char cmd[5];
scanf("%s", cmd);
if (cmd[0] == 'A') {
fa[++tot] = now;
addedge(now, tot);
now = tot;
scanf("%d%d", &a[now], &b[now]);
}
else now = fa[now];
id[t] = MK(i, head[now]);
head[now] = t++;
}
dfs(0);
memset(dp, 0x3f, sizeof(dp));
dp[0] = 0;
dfsdp(0, 0, 0, 0);
for (int i = 0; i < n; ++i)
printf("%d\n", ans[i]);
}
return 0;
}