[20220214聯考] 樹上的棋局
前言
這種才6k、200行出頭的程式碼不是隨便調一調就調過了嗎。【戰術後仰】
但是會不會做就是另外一回事了。
題目
給一棵 \(n\) 個點的樹,初始時根為 \(1\),每個節點上有個棋子。然後 Vi 和 Powder 開始博弈,博弈的規則很簡單,每個人在她的輪次時可以將某個棋子移到該棋子所在節點的子樹內任意一點(顯然不能是這個點本身)。
但是這個典中典問題兩人都很明白是 \(\tt SG\) 函式板題,所以她們又加入了 \(m\) 個奇怪的操作:
- \(u,v,x\) 表示在 \(u,v\) 的路徑上各放一個棋子,並把根換為 \(x\)。
- \(u,x\) 表示在 \(u\) 及其子樹內的節點各放一個棋子(注意此時的樹根),並把根換為 \(x\)
兩人在祖安混了這麼久,當然很懂博弈,於是她們想問你更本質的東西:此時的 \(\tt SG\) 函式值,而非先後手誰勝。
\(1\le n\le 2\times 10^5;0\le m\le 2\times 10^5.\)
講解
由於把題錯看成移動棋子是移動到某個兒子上而直接自閉去剛T1(然後被卡科技了)。
在沒看錯題的情況下,只要你稍微懂點 \(\tt SG\) 函式,我們就可以得出結論,每個節點的 \(\tt SG\) 函式值為該點到其子樹內點的最大距離。顯然同一個位置的 \(2\) 個棋子的 \(\tt SG\) 函式值為 \(0\),我們只需維護 \(0/1\) 值表示是否需要統計貢獻。
對於換根操作其實有個經典解決辦法(然而我不知道qwq):單獨處理新根到原根路徑上的貢獻,其它點的貢獻其實是不變的,可以直接用原來的。當然這個貢獻要只和自己的子樹有關才能這麼做,而這道題滿足條件。
考慮這條鏈怎麼求貢獻,卷爺說:直接重鏈剖分。
然後我們考慮跳重鏈的時候怎麼求貢獻,其實直接預處理每個節點當樹根在其重兒子子樹內時其 \(\tt SG\) 函式值即可。
但實際上我們預處理的是 \(fSG_i:i\) 為根時其父親的 \(SG\) 函式值,預處理這個是因為跳輕邊的時候也要用。
由於向上跳輕邊只有 \(\log_2n\) 級別個,可以直接暴力做。
總時間複雜度 \(O(n\log_2^2n)\)。
程式碼
就這?完全不夠寫啊!
//12252024832524 #include <bits/stdc++.h> #define TT template<typename T> using namespace std; typedef long long LL; const int MAXN = 200005; int n,m,rt = 1; LL Read() { LL x = 0,f = 1;char c = getchar(); while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();} while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();} return x * f; } TT void Put1(T x) { if(x > 9) Put1(x/10); putchar(x%10^48); } TT void Put(T x,char c = -1) { if(x < 0) putchar('-'),x = -x; Put1(x); if(c >= 0) putchar(c); } TT T Max(T x,T y){return x > y ? x : y;} TT T Min(T x,T y){return x < y ? x : y;} TT T Abs(T x){return x < 0 ? -x : x;} int head[MAXN],tot; struct edge { int v,nxt; }e[MAXN<<1]; void Add_Edge(int x,int y) { e[++tot] = edge{y,head[x]}; head[x] = tot; } void Add_Double_Edge(int x,int y) { Add_Edge(x,y); Add_Edge(y,x); } int d[MAXN],siz[MAXN],f[MAXN],MAX[MAXN][2],fSG[MAXN],son[MAXN]; //depth son_size father MAX0,1 father's SG heavy son(poor English void dfs1(int x,int fa) { d[x] = d[fa] + 1; siz[x] = 1; f[x] = fa; for(int i = head[x],v; i ;i = e[i].nxt) if((v = e[i].v) ^ fa) { dfs1(v,x),siz[x] += siz[v]; if(siz[v] > siz[son[x]]) son[x] = v; if(MAX[v][0]+1 > MAX[x][0]) MAX[x][1] = MAX[x][0],MAX[x][0] = MAX[v][0]+1; else if(MAX[v][0]+1 > MAX[x][1]) MAX[x][1] = MAX[v][0]+1; } } int dfn[MAXN],rdfn[MAXN],dfntot,tp[MAXN]; void dfs2(int x,int t) { tp[x] = t; rdfn[dfn[x] = ++dfntot] = x; if(f[x] > 1) fSG[x] = Max(fSG[x],fSG[f[x]]+1); if(!son[x]) return; if(MAX[son[x]][0]+1 == MAX[x][0]) fSG[son[x]] = MAX[x][1]; else fSG[son[x]] = MAX[x][0]; dfs2(son[x],t); for(int i = head[x],v; i ;i = e[i].nxt) if(((v = e[i].v) ^ f[x]) && (v ^ son[x])) { if(MAX[v][0]+1 == MAX[x][0]) fSG[v] = MAX[x][1]; else fSG[v] = MAX[x][0]; dfs2(v,v); } } #define lc (x<<1) #define rc (x<<1|1) int SG[MAXN<<2],ASG[MAXN<<2];//SG & all SG int ESG[MAXN<<2],AESG[MAXN<<2];//except heavy son's SG -> ESG bool lz[MAXN<<2];//lazy tag void calc(int x){lz[x] ^= 1; SG[x] ^= ASG[x]; ESG[x] ^= AESG[x];} void up(int x) { SG[x] = SG[lc] ^ SG[rc]; ESG[x] = ESG[lc] ^ ESG[rc]; } void down(int x) { if(!lz[x]) return; calc(lc); calc(rc); lz[x] = 0; } void Build(int x,int l,int r) { if(l == r) {ASG[x] = SG[x] = MAX[rdfn[l]][0];ESG[x] = AESG[x] = fSG[son[rdfn[l]]];return;} int mid = (l+r) >> 1; Build(lc,l,mid); Build(rc,mid+1,r); up(x); ASG[x] = ASG[lc] ^ ASG[rc]; AESG[x] = AESG[lc] ^ AESG[rc]; } void Add(int x,int l,int r,int ql,int qr) { if(ql <= l && r <= qr) {calc(x);return;} int mid = (l+r) >> 1; down(x); if(ql <= mid) Add(lc,l,mid,ql,qr); if(mid+1 <= qr) Add(rc,mid+1,r,ql,qr); up(x); } bool Querylz(int x,int l,int r,int pos) { if(l == r) return lz[x]; int mid = (l+r) >> 1; down(x); if(pos <= mid) return Querylz(lc,l,mid,pos); else return Querylz(rc,mid+1,r,pos); } int QuerySG(int x,int l,int r,int ql,int qr) { if(ql <= l && r <= qr) return SG[x]; int mid = (l+r) >> 1,ret = 0; down(x); if(ql <= mid) ret ^= QuerySG(lc,l,mid,ql,qr); if(mid+1 <= qr) ret ^= QuerySG(rc,mid+1,r,ql,qr); return ret; } int QueryESG(int x,int l,int r,int ql,int qr) { if(ql > qr) return 0; if(ql <= l && r <= qr) return ESG[x]; int mid = (l+r) >> 1,ret = 0; down(x); if(ql <= mid) ret ^= QueryESG(lc,l,mid,ql,qr); if(mid+1 <= qr) ret ^= QueryESG(rc,mid+1,r,ql,qr); return ret; } void Add_Chain(int u,int v) { while(tp[u] ^ tp[v]) { if(d[tp[u]] < d[tp[v]]) swap(u,v); Add(1,1,n,dfn[tp[u]],dfn[u]); u = f[tp[u]]; } if(d[u] > d[v]) swap(u,v); Add(1,1,n,dfn[u],dfn[v]); } void Add_SonTree(int x)//pot 3 { int now = rt; if(x == now) {Add(1,1,n,1,n);return;}//pot 4 while(d[now] > d[x] && tp[now] != tp[x]) now = f[tp[now]]; if(tp[now] != tp[x] || d[now] < d[x]) Add(1,1,n,dfn[x],dfn[x]+siz[x]-1); else { now = rt; while(f[now] != x && tp[now] != tp[x]) { now = tp[now]; if(f[now] ^ x) now = f[now]; } if(tp[now] == tp[x]) Add(1,1,n,dfn[son[x]],dfn[son[x]]+siz[son[x]]-1); else Add(1,1,n,dfn[now],dfn[now]+siz[now]-1); Add(1,1,n,1,n); } } int solve() { int ret = SG[1],now = rt; while(tp[now] ^ 1) { ret ^= QuerySG(1,1,n,dfn[tp[now]],dfn[now]); now = f[tp[now]]; } ret ^= QuerySG(1,1,n,1,dfn[now]); now = rt; //從這裡開始要保證now的貢獻是算了的 if(!Querylz(1,1,n,dfn[now])) ret ^= Max(MAX[now][0],now == 1 ? 0 : fSG[now]+1);//itself pot1,pot5,pot6(一個位置鍋三次是我沒想到的 while(tp[now] ^ 1) { ret ^= QueryESG(1,1,n,dfn[tp[now]],dfn[now]-1); now = tp[now]; if(!Querylz(1,1,n,dfn[f[now]])) ret ^= fSG[now]; now = f[now]; } ret ^= QueryESG(1,1,n,dfn[tp[now]],dfn[now]-1);//pot2 return ret; } int tmp[MAXN]; void print(int x,int l,int r) { if(l == r) {tmp[rdfn[l]] = lz[x];return;} int mid = (l+r) >> 1; down(x); print(lc,l,mid); print(rc,mid+1,r); } int main() { freopen("tree.in","r",stdin); freopen("tree.out","w",stdout); n = Read(); m = Read(); for(int i = 1;i < n;++ i) Add_Double_Edge(Read(),Read()); dfs1(1,0); dfs2(1,1); Build(1,1,n); while(m --> 0) { if(Read() == 1) Add_Chain(Read(),Read()),rt = Read(); else Add_SonTree(Read()),rt = Read(); Put(solve(),'\n'); } return 0; }