1. 程式人生 > 其它 >[The 2020 ICPC Asia Macau Regional Contest] I. Nim Cheater

[The 2020 ICPC Asia Macau Regional Contest] I. Nim Cheater

題目大意

開始是一個空的多重集,然後兩種操作:

  1. ADD a b: 加入一個數值為a,權值為b的元素。
  2. 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;
}