LOJ2269 [SDOI2017] 切樹遊戲 【FWT】【動態DP】【樹鏈剖分】【線段樹】
題目分析:
好題。本來是一道好的非套路題,但是不湊巧的是當年有一位國家集訓隊員正好介紹了這個算法。
首先考慮靜態的情況。這個的DP方程非常容易寫出來。
接著可以註意到對於異或結果的計數可以看成一個FWT的過程,進一步地可以註意到FWT在中途沒有還原的必要。從FWT的過程中我們可以發現FWT具有可加性和交換律結合律。
這樣原問題可以在靜態的情況下通過樹形DP做到$O(nm)$。
考慮動態的問題。根據《神奇的子圖》命題報告及其拓展中描述的算法五,我們應該不難想到基於樹鏈剖分的這樣的做法。
首先對樹進行輕重路徑剖分,構建一棵重鏈樹。接著將樹中輕子樹的影響放進對應的重鏈父節點(不是top節點)上。接著在線段樹上合並問題的答案。
對於一個線段樹中的結點,經過分析我們應該可以知道需要記錄的信息有這麽幾個:
L:從這個線段樹區間所對應的子重鏈的頂端開始延伸出的包含輕邊子樹的所有情況的FWT值。
R:從這個線段樹區間所對應的子重鏈的底端開始延伸出的包含輕邊子樹的所有情況的FWT值。
C:這個線段樹區間所對應的子重鏈上的所有點都被選擇,接著往子樹方向延伸的所有情況的FWT值。
tot:這個線段樹區間所對應的子重鏈以及它的所有輕邊子樹看作一個整體的樹的答案的FWT值。
有了這些信息之後線段樹的合並答案就不難寫出來了。我們用下標$0$表示左子樹,下標$1$表示右子樹。
$L = L_0+C_0*L_1$
$R = R_1+C_1*R_0$
$C = C_0*C_1$
$tot = tot_0+tot_1+L_1*R_0$
我們還提到了線段樹中的葉子結點是包含它的所有輕邊子樹信息的。根據《神奇的子圖》命題報告及其拓展中的描述,我們能夠想到通過普通的DP來轉移答案。
下面我們用H來表示它的一個輕邊兒子,Now表示葉子對應的原樹上的點,Base為128個單位FWT之一。註意到對於葉子結點,他的L和R和C是相同的。所以忽略R和C。這樣葉子的轉移可以這樣寫:
$Leaf_L = Base[v[Now]]*\prod_{i \in son}(H_L+Base[0])$可以理解為01背包的選和不選。
$Leaf_{tot} = \sum_{i \in son}tot_i + Leaf_L$.
有了它們,我們現在可以進行修改了。
對於一次修改,我們要做的是,沿著重鏈樹往上跳,遇到一條重鏈則更改重鏈在線段樹中的信息。這樣做只會經過$O(logn)$條重鏈,每條重鏈只會修改一個點,對應的在線段樹上只會修改一個點,線段樹每次修改時間復雜度為$O(logn)$,所以這是$O(log^2n)$的。
除以0的問題可以另外建一棵線段樹解決,但大家好像都說可以記錄0的個數,我不會,希望會的可以留個言。
時間復雜度$O(nmlog^2n)$,樹鏈剖分常數較小,可以通過所有數據。
upd:LOJ時間倒數第三。。。
代碼:
1 #include<bits/stdc++.h> 2 using namespace std; 3 4 const int maxn = 30200; 5 const int maxm = 128; 6 const int mod = 10007; 7 8 int n,m,num,num2,bfsnum; 9 int v[maxn]; 10 vector <int> g[maxn]; 11 12 vector <int> Chain[maxn]; 13 14 int dep[maxn],fa[maxn],top[maxn],number[maxn],sz[maxn],son[maxn]; 15 int tail[maxn],where[maxn],im[maxn],bfsin[maxn],bfsout[maxn]; 16 17 struct func{ 18 int cont[maxm]; 19 func operator * (func b){ 20 for(int i=0;i<=maxm;i++) b.cont[i] = (b.cont[i]*cont[i])%mod; 21 return b; 22 } 23 func operator + (func b){ 24 for(int i=0;i<=maxm;i++) b.cont[i] = (b.cont[i]+cont[i])%mod; 25 return b; 26 } 27 func operator - (func b){ 28 for(int i=0;i<=maxm;i++) b.cont[i] = (cont[i]-b.cont[i]+mod)%mod; 29 return b; 30 } 31 }base[maxm],T2[maxn<<2],Newnumber; 32 33 struct node{ 34 func L,R,C,tot; 35 //左端延伸,右端延伸,總和 36 }T[maxn<<2]; 37 38 void push_up(int now){ 39 T[now].L = T[now<<1].C*T[now<<1|1].L + T[now<<1].L; 40 T[now].R = T[now<<1|1].C*T[now<<1].R + T[now<<1|1].R; 41 T[now].C = T[now<<1].C*T[now<<1|1].C; 42 T[now].tot = T[now<<1].tot+T[now<<1|1].tot+T[now<<1].R*T[now<<1|1].L; 43 } 44 45 void dfs1(int now,int f,int dp){ 46 dep[now] = dp; fa[now] = f; 47 for(int i=0;i<g[now].size();i++){ 48 if(g[now][i] == f) continue; 49 dfs1(g[now][i],now,dp+1); 50 sz[now] += sz[g[now][i]]; 51 if(sz[son[now]] < sz[g[now][i]]) son[now] = g[now][i]; 52 } 53 sz[now]++; 54 } 55 56 void dfs2(int now,int tp){ 57 top[now] = tp; number[now] = ++num; where[num] = now; 58 if(now == tp && now!=1){Chain[top[fa[now]]].push_back(now);} 59 if(sz[now] != sz[son[now]] + 1){ 60 bfsin[now] = bfsnum+1; 61 if(fa[now] != 0) bfsout[now] = bfsnum+g[now].size()-2; 62 else bfsout[now] = bfsnum+g[now].size()-1; 63 }else bfsin[now] = 1,bfsout[now] = -1; 64 for(int i=0;i<g[now].size();i++){ 65 if(g[now][i] == fa[now] || g[now][i] == son[now]) continue; 66 im[g[now][i]] = ++bfsnum; 67 } 68 if(son[now]) {dfs2(son[now],tp);tail[now]=tail[son[now]];} 69 else tail[now] = now; 70 for(int i=0;i<g[now].size();i++){ 71 if(g[now][i] == fa[now] || g[now][i] == son[now]) continue; 72 dfs2(g[now][i],g[now][i]); 73 } 74 } 75 76 node merge(node alpha,node beta){ 77 node gamma; gamma.L = alpha.L + beta.L*alpha.C; 78 gamma.R = beta.R + alpha.R*beta.C; 79 gamma.C = alpha.C*beta.C; 80 gamma.tot = alpha.tot+beta.tot+alpha.R*beta.L; 81 return gamma; 82 } 83 84 node Query(int now,int tl,int tr,int l,int r){ 85 if(tl >= l && tr <= r) return T[now]; 86 int mid = (tl+tr)/2; 87 if(mid >= r) return Query(now<<1,tl,mid,l,r); 88 if(mid < l) return Query(now<<1|1,mid+1,tr,l,r); 89 node ans1 = Query(now<<1,tl,mid,l,r); 90 node ans2 = Query(now<<1|1,mid+1,tr,l,r); 91 return merge(ans1,ans2); 92 } 93 94 void build_tree(int now,int tl,int tr,int l,int r){ 95 if(tl > r || tr < l) return; 96 if(tl == tr){ 97 tl = where[tl]; T[now].L = base[v[tl]]; 98 for(int i=0;i<g[tl].size();i++){ 99 if(g[tl][i] == fa[tl] || g[tl][i] == son[tl]) continue; 100 int SS = g[tl][i]; 101 node forw = Query(1,1,n,number[SS],number[tail[SS]]); 102 T[now].tot = T[now].tot + forw.tot; 103 T[now].L = T[now].L*(base[0]+forw.L); 104 } 105 T[now].R = T[now].C = T[now].L; 106 T[now].tot = T[now].tot + T[now].L; 107 }else{ 108 int mid = (tl+tr)/2; 109 build_tree(now<<1,tl,mid,l,r); 110 build_tree(now<<1|1,mid+1,tr,l,r); 111 push_up(now); 112 } 113 } 114 115 void dfs3(int now){ 116 for(int i=0;i<Chain[now].size();i++){ dfs3(Chain[now][i]); } //sub 117 int p = tail[now]; 118 build_tree(1,1,n,number[now],number[tail[now]]); 119 } 120 121 void read(){ 122 scanf("%d%d",&n,&m); 123 for(int i=1;i<=n;i++) scanf("%d",&v[i]); 124 for(int i=1;i<n;i++){ 125 int x,y; scanf("%d%d",&x,&y); 126 g[x].push_back(y); g[y].push_back(x); 127 } 128 } 129 130 void FWT(func &now,int data){ 131 now.cont[data] = 1; 132 for(int i=2;i<=maxm;i<<=1){ 133 int yum = maxm/i; 134 for(int j=0;j<maxm;j+=yum*2){ 135 for(int k=0;k<yum;k++){ 136 int x = now.cont[j+k],y = now.cont[j+k+yum]; 137 now.cont[j+k] = x+y; now.cont[j+k+yum] = (x-y+mod)%mod; 138 } 139 } 140 } 141 } 142 143 void IFWT(func &now){ 144 for(int i=1;i<maxm;i<<=1){ 145 for(int j=0;j<=maxm;j+=i*2){ 146 for(int k=0;k<i;k++){ 147 int x = now.cont[j+k],y = now.cont[j+k+i]; 148 now.cont[j+k] = ((x+y)*5004)%mod; 149 now.cont[j+k+i] = ((x-y+mod)*5004)%mod; 150 } 151 } 152 } 153 } 154 155 func VQuery(int now,int tl,int tr,int l,int r){ 156 if(tl >= l && tr <= r) return T2[now]; 157 if(tl > r || tr < l) return base[0]; 158 int mid = (tl+tr)/2; 159 return VQuery(now<<1,tl,mid,l,r)*VQuery(now<<1|1,mid+1,tr,l,r); 160 } 161 162 void VModify(int now,int tl,int tr,int place){ 163 if(tl == tr){ 164 T2[now] = Newnumber; 165 }else{ 166 int mid = (tl+tr)/2; 167 if(place <= mid) VModify(now<<1,tl,mid,place); 168 else VModify(now<<1|1,mid+1,tr,place); 169 T2[now] = T2[now<<1]*T2[now<<1|1]; 170 } 171 } 172 173 void Modify(int now,int tl,int tr,int place){ 174 if(tl == tr){ 175 tl = where[tl]; 176 func res = VQuery(1,1,bfsnum,bfsin[tl],bfsout[tl]); 177 T[now].tot = T[now].tot - T[now].L; 178 T[now].L = T[now].R = T[now].C = res*base[v[tl]]; 179 T[now].tot = T[now].tot + T[now].L; 180 }else{ 181 int mid = (tl+tr)/2; 182 if(place <= mid) Modify(now<<1,tl,mid,place); 183 else Modify(now<<1|1,mid+1,tr,place); 184 push_up(now); 185 } 186 } 187 188 void Erase(int now,int tl,int tr,int place,int dr){ 189 if(tl == tr){ 190 tl = where[tl]; 191 if(dr == 1)T[now].tot = T[now].tot - Newnumber; 192 else T[now].tot = T[now].tot + Newnumber; 193 }else{ 194 int mid = (tl+tr)/2; 195 if(place <= mid) Erase(now<<1,tl,mid,place,dr); 196 else Erase(now<<1|1,mid+1,tr,place,dr); 197 push_up(now); 198 } 199 } 200 201 void work(){ 202 for(int i=0;i<maxm;i++) FWT(base[i],i); 203 dfs1(1,0,1); 204 dfs2(1,1); 205 dfs3(1); 206 for(int i=1;i<=n;i++){ 207 if(im[i]){ 208 Newnumber=Query(1,1,n,number[i],number[tail[i]]).L + base[0]; 209 VModify(1,1,bfsnum,im[i]); 210 } 211 } 212 int q; scanf("%d",&q); 213 for(int i=1;i<=q;i++){ 214 char str[10]; scanf("%s",str); 215 if(str[0] == ‘C‘){ 216 int x,y; scanf("%d%d",&x,&y); 217 int now = x;v[x] = y; 218 stack<int> sta; 219 while(fa[top[now]]!=0){sta.push(top[now]); now=fa[top[now]];} 220 while(!sta.empty()){ // clear tot 221 int hd = sta.top();sta.pop(); 222 Newnumber = Query(1,1,n,number[hd],number[tail[hd]]).tot; 223 Erase(1,1,n,number[fa[hd]],1); 224 } now = x; 225 while(now != 0){ 226 Modify(1,1,n,number[now]); 227 now = top[now]; 228 Newnumber = Query(1,1,n,number[now],number[tail[now]]).tot; 229 if(fa[top[now]]) Erase(1,1,n,number[fa[now]],0); 230 Newnumber=base[0]+Query(1,1,n,number[now],number[tail[now]]).L; 231 if(im[now]) VModify(1,1,bfsnum,im[now]); 232 now = fa[now]; 233 } 234 }else{ 235 int k; scanf("%d",&k); 236 func ans = Query(1,1,n,number[1],number[tail[1]]).tot; 237 IFWT(ans); 238 printf("%d\n",ans.cont[k]); 239 } 240 } 241 } 242 243 int main(){ 244 read(); 245 work(); 246 return 0; 247 }
LOJ2269 [SDOI2017] 切樹遊戲 【FWT】【動態DP】【樹鏈剖分】【線段樹】