Trie树合并 + SG函数 ---- BZOJ4730. Alice和Bob又在玩游戏(动态开点Trie 树上全局异或标记 + 合并 + 博弈论)

题目大题


题目大意:

在这里插入图片描述


解题思路:

  1. 首先我们对于子树u的 S G 函 数 为 SG函数为 SG
    ⨁ 是 异 或 和 \bigoplus是异或和
    S G [ u ] = m e x { ⨁ w ∈ ( w 的 父 亲 在 u 到 v 的 路 径 上 ) S G [ w ] ∣ v ∈ ( u 的 子 树 里 面 的 点 ) } SG[u]=mex\{\bigoplus_{w\in(w的父亲在u到v的路径上)}SG[w]|v\in(u的子树里面的点)\} SG[u]=mex{w(wuv)SG[w]v(u)}

通俗一点就是就是枚举 u u u所有的里面的子树节点 v v v然后把分裂出来的子树 w w w S G SG SG函数 ⊕ \oplus 起来就好了
但是这样的复杂度是 O ( n 3 ) O(n^3) O(n3)的,我们想办法优化一下?

  1. 我们看这个 S G 函 数 SG函数 SG是自底向上跟新的,那么我们从底向上考虑:
  2. 我们定义
    a u = ⨁ v 是 u 的 直 接 儿 子 S G [ v ] a_u=\bigoplus_{v是u的直接儿子}SG[v] au=vuSG[v]
    b u = ⨁ v 是 u 直 接 儿 子 并 且 包 括 u S G [ v ] b_u=\bigoplus_{v是u直接儿子并且包括u}SG[v] bu=vuuSG[v]
    只考虑相邻两层
    在这里插入图片描述

那么上面的公式就可以变成:根据异或的消去律 对于一个 v v v

a u    ⊕    ( ⨁ w 是 u 和 v 的 路 径 上 面 的 点 & & w 不 等 于 v b w ) a_u\;\oplus\;(\bigoplus_{w是u和v的路径上面的点\&\&w不等于v}b_w) au(wuv&&wvbw)
手模一下你们发现就只剩下点 1 , 2 , 3 , 4 了 1,2,3,4了 1,2,3,4就刚好是
在这里插入图片描述
那么式子就成了。

⨁ w ∈ ( w 的 父 亲 在 u 到 v 的 路 径 上 ) S G [ w ] = ( ⨁ w 是 u 和 v 的 路 径 上 面 的 点 & & w 不 等 于 v b w ) ⊕ a u \bigoplus_{w\in(w的父亲在u到v的路径上)}SG[w]=(\bigoplus_{w是u和v的路径上面的点\&\&w不等于v}b_w)\oplus a_u w(wuv)SG[w]=(wuv&&wvbw)au

  • 假设有一棵二进制意义下的 Trie 树,其中储存了所有 u u u 的子树内的 v v v 对应的 ⨁ w 是 u 和 v 的 路 径 上 面 的 点 b w \bigoplus_{w是u和v的路径上面的点}b_w wuvbw.

  • 易得我们需要求得一个最小的数 r e s res res ,使得 a u a_u au异或上 Trie 树储存的任意一个数都不能得到 r e s res res.

  • 考虑在 Trie 树上从上往下贪心(从高到低确定 r e s res res的每一位):设当前要确定第 i i i 位(这里二进制位由低往高,最低位为第 0 0 0 位),现在走到 Trie 树的节点 x x x a u a_u au的第 i i i 位为 1 1 1 的时候,我们考虑 r e s r e s res 的第 i i i 位可以为 0 0 0 的条件:

  • x x x 的右子节点(由字符为 1 1 1 的边转移到的子节点)对应的子树不是满二叉树,即子树内的叶子数小于 2 i 2^i 2i可以在 Trie 的每个节点上储存(一个 s i z e size size 表示子树内的叶子个数)这时候我们就可以把 r e s r e s res 的第 i i i 位设为 0 0 0 ,否则设为 1 1 1

  • a u a_u au的第 i i i 位为 0 0 0 同理,当 x x x 到达了叶子节点之后就得到了 S G [ u ] = r e s S G [ u ] = r e s SG[u]=res

  • 但我们显然不能直接把这棵 Trie 建出来,否则这个 DP 不能得到复杂度上的优化

  • 我们考虑一下父子关系:就是自顶向上合并的时候我们发现 ⨁ w 是 u 和 v 的 路 径 上 面 的 点 & & w 不 等 于 v b w \bigoplus_{w是u和v的路径上面的点\&\&w不等于v}b_w wuv&&wvbw里面 w 不 等 于 v w不等于v wv那么我们向上时候可以把 ( b w [ w = v ] = S G [ v ] ⊕ a u ) 异 或 上 去 (b_w[w=v]=SG[v]\oplus a_u)异或上去 (bw[w=v]=SG[v]au)然后再把所有的 f a [ v ] fa[v] fa[v]的所有儿子节点的字典树都合并起来就可以了

  • 这个你手模一下父子关系就可以发现了

  • 这里是全局的异或 S G [ v ] ⊕ a u SG[v]\oplus a_u SG[v]au,我们要在字典树上面打标记类似线段树吧很好理解,每次递归时候下传就可以了
    下放标记的具体方法:如果要下放 Trie 树节点 x x x上的标记 t a g t a g tag ,且连接 x x x x x x 的子节点的转移边表示第 i i i 位,则把 x x x 的左子节点的标记和右子节点的标记都异或上 t a g t a g tag如果 t a g t a g tag 的第 i i i 位为 1 1 1需要交换 x x x 的左右子节点(因为这时异或上 t a g t a g tag相当于第 i i i 位被取反),最后将 x x x 节点上的标记清空复杂度 O ( T n l o g ⁡ ⁡ n ) O ( T n log ⁡ ⁡ n ) O(Tnlogn)

  • 这么为了不能全局动态开点,我们可以先在里面插入一个 0 0 0


AC code

#include <bits/stdc++.h>
#define mid ((l + r) >> 1)
#define Lson rt << 1, l , mid
#define Rson rt << 1|1, mid + 1, r
#define ms(a,al) memset(a,al,sizeof(a))
#define log2(a) log(a)/log(2)
#define lowbit(x) ((-x) & x)
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define INF 0x3f3f3f3f
#define LLF 0x3f3f3f3f3f3f3f3f
#define f first
#define s second
#define endl '\n'
using namespace std;
const int N = 7e6 + 10, mod = 1e9 + 9;
const int maxn = 500010;
const long double eps = 1e-5;
const int EPS = 500 * 500;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> PII;
typedef pair<ll,ll> PLL;
typedef pair<double,double> PDD;
template<typename T> void read(T &x) {
   x = 0;char ch = getchar();ll f = 1;
   while(!isdigit(ch)){if(ch == '-')f*=-1;ch=getchar();}
   while(isdigit(ch)){x = x*10+ch-48;ch=getchar();}x*=f;
}
template<typename T, typename... Args> void read(T &first, Args& ... args) {
   read(first);
   read(args...);
}
int n, m, tot;
int fa[N];
vector<int> G[N];

struct Trie {
    int lc, rc, tag, sze;
    void init() {
        lc = rc = tag = sze = 0;
    }  
}T[N];
int SG[N], rt[N];
inline int find(int x) {
    return fa[x] == x ? x : fa[x] = find(fa[x]);
}
inline void init() {
    for(int i = 1; i <= n; ++ i) fa[i] = i, G[i].clear();
    tot = 0;
}

void downdate(int x, int i) {
    if((T[x].tag>>i)&1) swap(T[x].lc,T[x].rc);
    if(T[x].lc) T[T[x].lc].tag ^= T[x].tag;
    if(T[x].rc) T[T[x].rc].tag ^= T[x].tag;
}

int merge(int i, int x, int y) {
    if(!x || !y) return x + y;
    if(i == -1) return x;
    downdate(x,i), downdate(y,i);
    T[x].lc = merge(i-1,T[x].lc,T[y].lc);
    T[x].rc = merge(i-1,T[x].rc,T[y].rc);
    T[x].sze = T[T[x].lc].sze + T[T[x].rc].sze;
    return x;
}

int min_mex_xor(int x, int num) {
    int res = 0;
    for(int i = 30; i >= 0; -- i) {
        downdate(x,i);
        if(num >> i & 1) {
            if(T[T[x].rc].sze < (1 << i)) x = T[x].rc;
            else x = T[x].lc, res |= 1 << i;
        } else {
            if(T[T[x].lc].sze < (1 << i)) x = T[x].lc;
            else x = T[x].rc, res |= 1 << i;
        }
    }
    return res;
}

void ins(int i, int &x, int num ) { 
    if(!x) T[x = ++ tot].init();
    if(i == -1) return (void) (T[x].sze = 1);
    downdate(x,i);
    if(num >> i & 1) ins(i-1,T[x].rc,num);
    else ins(i-1,T[x].lc,num);
    T[x].sze = T[T[x].lc].sze + T[T[x].rc].sze;
}

inline int dfs(int u, int fa) {
    int au = 0;
    rt[u] = 0;
    ins(30,rt[u],0);
    for(auto it : G[u]) {
        if(it == fa) continue;
        dfs(it,u);
        merge(30,rt[u],rt[it]);
        au ^= SG[it];
    }
    SG[u] = min_mex_xor(rt[u],au);
    T[rt[u]].tag ^= (au ^ SG[u]);
    return SG[u];
}

int main() {
    IOS;
    int _;
    cin >> _;
    while(_--) {
        cin >> n >> m;
        init();
        for(int i = 1; i <= m; ++ i) {
            int u, v;
            cin >> u >> v;
            G[u].push_back(v);
            G[v].push_back(u);
            int fu = find(u), fv = find(v);
            if(fu != fv) {
                if(fu > fv) swap(fu,fv);
                fa[fv] = fu;
            }
        }
        int ans = 0;
        for(int i = 1; i <= n; ++ i) 
          if(find(i) == i) 
            ans ^= dfs(i,0);
        if(ans) cout << "Alice\n";
        else cout << "Bob\n";
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值