「解题报告」AGC064C Erase and Divide Game

发布时间 2023-08-14 07:35:23作者: APJifengc

第二次打 AGC,场上过了 C,还是很开心的。

而且是在一整天没碰 OI 的情况下。所以长时间 AFO 再回来打比赛会不会对 OI 有一定的 buff?有时候思维过度疲惫的情况下貌似打比赛脑子是真的转不动。

不过为啥 D 过的比 C 多啊,我觉得 C 很简单啊,基本没有任何转化,可能 AT 的这类计数题我还是做的不多吧。


考虑数很少怎么做。如果我们把所有数按照二进制从低位到高位加入到一棵 01 Trie 内,那么我们实际上就是在这棵 01 Trie 上进行博弈。这个是很容易的,我们可以随便 DFS 一下跑一个博弈 DP 得出结果。

此时的难点在于给出的不是数,而是数的区间。不过我们仍然可以用经典套路,去将一个区间进行二进制分组,把所有区间划分成 \(\lbrack k \times 2^m, (k + 1) \times 2^m)\) 的形式,然后再插入。但是此时我们意识到一件事情,就是我们的二进制是倒过来插入的,于是这时候这样的一个区间并不是一个满二叉树,我们就不能用经典的将左右儿子全部指向一个点来优化建图了。那怎么做?

还是考虑这样的一个区间的性质,发现这本质上是对所有深度为 \(m\) 的节点的子树内插入了一个 \(k\)。而发现重要的一点,就是在没有删到这个深度之前,前面的操作对这个子树内的数字是没有任何影响的。那么我们可以考虑按照 \(k\) 分开,对每一个 \(k\) 建一棵 01 Trie,那么我们相当于同时在这 \(\log V\) 棵 Trie 上进行博弈。但是这样复杂度显然不能接受,考虑减少状态数。

此时我们发现一件重要的事情:如果我们知道我们当前在第 \(0\) 棵 Trie 上的节点,那么我们是可以推出后面所有 Trie 的节点的。因为知道节点就相当于知道了之前走的路径。那么我们就只需要记录当前走到了第 \(0\) 棵 Trie 上的哪个节点即可。但是这还不够,因为有可能当前走的路径在第 \(0\) 棵 Trie 上不存在,但是在后面的某一棵 Trie 上是存在的,那么我们可以将状态记录在第一棵存在当前路径节点的子树。这样如果我们知道这个节点,我们也可以推出这个节点往后的所有节点。那么这样我们就将状态压缩到了 \(O(n \log^2 V)\),可以通过。

我的实现有点类似于 AC 自动机的感觉,将所有空的路径指向了下一个存在这条路径的子树上的节点。

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 36000005;
int T;
int n;
int t[MAXN][2];
bool f[MAXN];
int tot;
int root[66];
void init(int x) { t[x][0] = t[x][1] = f[x] = 0; }
void insert(long long x, int pt) {
    int p = root[pt];
    for (int i = 0; i <= 59 - pt; i++) {
        if (!t[p][x >> i & 1]) init(++tot), t[p][x >> i & 1] = tot;
        p = t[p][x >> i & 1];
    }
}
void dfs(int u, int v) { // u 表示当前节点,v 表示后面第一个存在当前路径的子树上的节点
    for (int c = 0; c <= 1; c++) {
        int w = v < 0 ? -v : t[v][c];
        if (!t[u][c]) {
            t[u][c] = w;
        } else {
            dfs(t[u][c], w);
        }
        if (!f[t[u][c]]) f[u] = 1;
    }
}
int main() {
    scanf("%d", &T);
    while (T--) {
        scanf("%d", &n);
        tot = 0;
        for (int i = 0; i <= 59; i++) init(root[i] = ++tot);
        for (int i = 1; i <= n; i++) {
            long long l, r; scanf("%lld%lld", &l, &r);
            r++;
            for (int i = 0; i <= 59; i++) if ((l >> i & 1) && (l + (1ll << i) <= r)) {
                insert(l >> i, i);
                l += 1ll << i;
            }
            for (int i = 59; i >= 0; i--) if (l + (1ll << i) <= r) {
                insert(l >> i, i);
                l += 1ll << i;
            }
        }
        bool flag = false;
        for (int i = 59; i >= 0; i--) {
            int u = root[i];
            flag |= t[u][0] || t[u][1];
            if (flag) dfs(root[i], -root[i + 1]);
            else root[i] = 0;
        }
        printf(f[root[0]] ? "Takahashi\n" : "Aoki\n");
    }
    return 0;
}