【CF613D】Kingdom and its Cities(重拾虛樹)
阿新 • • 發佈:2020-10-29
- 給定一棵\(n\)個點的樹。\(q\)組詢問,每次選出\(k\)個關鍵點。
- 問最少刪除多少非關鍵點才能使得關鍵點兩兩不連通。
- \(n,\sum k\le10^5\)
虛樹
既然這篇部落格打著“重拾虛樹”的名號,自然要提一提虛樹了。。。
考慮我們把所有點按\(dfs\)序排序,則原本所有的點+排序後相鄰兩點間的\(LCA\)就是最終虛樹所需的點。
然後我們把這些點再排序一次,並開一個棧,接著列舉每一個點。
- 首先,把棧中所有不是當前點祖先的點彈掉。則最後的棧頂就是當前點在虛樹上的父節點。
- 然後,把當前點加到棧中。
感覺還是比較容易理解的。
簡單的求解
這道題建出虛樹後剩下的就很簡單了。
首先很容易判無解,就是看是否有兩個關鍵點直接相連。
不然,我們令\(g_x\)表示\(x\)子樹內仍然聯通的點數,根據\(x\)是否為關鍵點分類討論:
- \(x\)為關鍵點。顯然要把所有點斷開,給答案加上\(g_x\),同時給\(g_{anc[x]}\)加上\(1\)(無法斷開當前點)。
- \(x\)為非關鍵點。又要分三類討論:
- \(g_x=0\):無需操作。
- \(g_x=1\):注意我們在這裡把它斷開顯然不會更優,不如轉送給父節點,因此給\(g_{anc[x]}\)加上\(1\)。
- \(g_x>1\):我們不得不把當前點斷開,因此給\(ans\)加上\(1\)。
程式碼:\(O((n+\sum k)logn)\)
#include<bits/stdc++.h> #define Tp template<typename Ty> #define Ts template<typename Ty,typename... Ar> #define Reg register #define RI Reg int #define Con const #define CI Con int& #define I inline #define W while #define N 100000 #define LN 20 #define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y) using namespace std; int n,ee,lnk[N+5];struct edge {int to,nxt;}e[N<<1]; namespace VirtualTree//虛樹 { int cnt,o[N+5],d,dI[N+5],dO[N+5],dep[N+5],fa[N+5][LN+5]; I bool cmp(CI x,CI y) {return dI[x]<dI[y];}//根據dfs序排序 I int LCA(RI x,RI y)//倍增LCA { RI i;dep[x]<dep[y]&&(x^=y^=x^=y); for(i=0;dep[x]^dep[y];++i) (dep[x]^dep[y])>>i&1&&(x=fa[x][i]);if(x==y) return x; for(i=LN;~i;--i) fa[x][i]^fa[y][i]&&(x=fa[x][i],y=fa[y][i]);return fa[x][0]; } I void dfs(CI x=1,CI lst=0)//預處理 { RI i;for(dI[x]=++d,i=1;i<=LN;++i) fa[x][i]=fa[fa[x][i-1]][i-1]; for(i=lnk[x];i;i=e[i].nxt) e[i].to^lst&& (dep[e[i].to]=dep[fa[e[i].to][0]=x]+1,dfs(e[i].to,x),0);dO[x]=d; } int vis[N+5],S[N+5],anc[N+5],g[N+5];I void Solve()//求解一次詢問的答案 { RI i;for(sort(o+1,o+cnt+1,cmp),i=1;i<=cnt;++i) vis[o[i]]=1;//vis=1表示關鍵點 #define Ins(x) (!vis[x]&&(vis[o[++cnt]=x]=2))//vis=2表示虛樹上的非關鍵點 RI x,t=cnt,ans=0;for(i=1;i^t;++i) x=LCA(o[i],o[i+1]),Ins(x);//加入相鄰兩點LCA RI T=0;for(sort(o+1,o+cnt+1,cmp),i=1;i<=cnt;++i)//排序後列舉建樹 {W(T&&dO[o[S[T]]]<dI[o[i]]) --T;anc[i]=S[T],S[++T]=i;}//維護一個棧,求出各點在虛樹上的父節點 for(i=2;i<=cnt;++i) if(vis[o[anc[i]]]==1) if(vis[o[i]]==1&&dep[o[anc[i]]]+1==dep[o[i]]) {puts("-1");goto Cl;}//判無解 for(i=cnt;i;--i) vis[o[i]]==1?(ans+=g[i],++g[anc[i]]):g[i]&&(g[i]==1?++g[anc[i]]:++ans);//從葉節點向上轉移 printf("%d\n",ans);Cl:for(i=1;i<=cnt;++i) vis[o[i]]=g[i]=0;cnt=0;//注意多組詢問要清空 } } int main() { using namespace VirtualTree; RI i,x,y;for(scanf("%d",&n),i=1;i^n;++i) scanf("%d%d",&x,&y),add(x,y),add(y,x);dfs();//讀入全樹 RI Qt;scanf("%d",&Qt);W(Qt--) {for(scanf("%d",&x);x;--x) scanf("%d",o+(++cnt));Solve();}//處理詢問 return 0; }