「CEOI2020」春季大掃除 題解
阿新 • • 發佈:2021-08-28
題意
\(~~~~\) 給出若干次詢問,每次詢問將對若干個點增加葉子結點(同一個點可能加多次),定義一組葉子結點匹配的權值為這兩點間路徑經過的邊數,求最小的匹配權值。
\(~~~~\) \(1\leq n\leq 10^5,1\leq \sum D_i \leq 10^5\)
本文版權歸 Azazel 與部落格園共有,歡迎轉載,但需保留此宣告,並給出原文地址,謝謝合作。
原文地址:https://www.cnblogs.com/Azazel/p/15192326.html
題解
\(~~~~\) 首先,葉子結點必須是偶數時才有解,因為奇數時至少會存在一個葉子其到父親的邊不能被清掃。
\(~~~~\)
- 當子樹內葉子結點為奇數時,該子樹根節點到父親的邊會被清掃一次;
- 當子樹內葉子結點為偶數時,該子樹根節點到父親的邊會被清掃兩次;
\(~~~~\) 考慮證明這個結論的正確性:每個點(除去欽定的根節點)到父親的邊都應該被清掃至少一次。當子樹內只剩一個葉子結點未匹配時,外界也會有奇數個葉子結點供該點匹配,偶數同理。當每個點(除去欽定的根節點)到父親的邊都被清掃,我們就可以認為整棵樹被清掃了。
\(~~~~\) 以上是一個 \(\mathcal{o(nq)}\) 的做法,考慮我們需要的資訊只有每個點子樹內的葉子結點數量 \(\bmod 2\),故我們用樹剖維護,對於某個點若其為葉子結點則不管,否則將該點到根節點的路徑上所有點的點權 \(\oplus 1\)
程式碼
檢視程式碼
#include <cstdio> #include <vector> #include <algorithm> using namespace std; int n,q,m; int siz[100005],dfn[100005],top[100005],sizLeaf[100005]; int dep[100005],fa[100005],To[100005],Times,son[100005],deg[100005]; bool IsLeaf[100005]; vector <int> G[100005]; void dfs1(int u,int Fa) { sizLeaf[u]=IsLeaf[u];fa[u]=Fa; siz[u]=1;dep[u]=dep[Fa]+1; for(int i=0;i<G[u].size();i++) { int v=G[u][i]; if(v==Fa) continue; dfs1(v,u); siz[u]+=siz[v];sizLeaf[u]=(sizLeaf[u]+sizLeaf[v])%2; if(siz[v]>siz[son[u]]) son[u]=v; } } void dfs2(int u,int T) { top[u]=T;dfn[u]=++Times;To[Times]=u; if(!son[u]) return; dfs2(son[u],T); for(int i=0;i<G[u].size();i++) { int v=G[u][i]; if(v==fa[u]||v==son[u]) continue; dfs2(v,v); } } struct SegmentTree{ #define ls p<<1 #define rs p<<1|1 #define lson p<<1,l,mid #define rson p<<1|1,mid+1,r int tr[400005],tag[400005]; void pushUp(int p){tr[p]=tr[ls]+tr[rs];} void pushDown(int p,int l,int r) { if(tag[p]) { int mid=(l+r)>>1; tr[ls]=(mid-l+1)-tr[ls]; tr[rs]=(r-mid)-tr[rs]; tag[ls]^=1; tag[rs]^=1; tag[p]^=1; } } void Build(int p,int l,int r) { if(l==r) { tr[p]=sizLeaf[To[l]]; return; } int mid=(l+r)>>1; Build(lson); Build(rson); pushUp(p); } void Modify(int p,int l,int r,int lx,int rx) { if(lx<=l&&r<=rx) { tr[p]=(r-l+1)-tr[p]; tag[p]^=1; return; } int mid=(l+r)>>1;pushDown(p,l,r); if(lx<=mid) Modify(lson,lx,rx); if(mid<rx) Modify(rson,lx,rx); pushUp(p); } int Query(int p,int l,int r,int lx,int rx) { if(lx<=l&&r<=rx) return tr[p]; int mid=(l+r)>>1,ret=0;pushDown(p,l,r); if(lx<=mid) ret+=Query(lson,lx,rx); if(mid<rx) ret+=Query(rson,lx,rx); return ret; } }Seg; void ModifyPath(int x,int y) { // printf("%d %d:",x,y); while(top[x]!=top[y]) { if(dep[top[x]]<dep[top[y]]) swap(x,y); Seg.Modify(1,1,n,dfn[top[x]],dfn[x]); x=fa[top[x]]; } if(dep[x]>dep[y]) swap(x,y); Seg.Modify(1,1,n,dfn[x],dfn[y]); // for(int i=1;i<=n;i++) printf("%d ",Seg.Query(1,1,n,dfn[i],dfn[i]));puts(""); } vector <int> Changed,V; int main() { // freopen("1.in","r",stdin); // freopen("1.out","w",stdout); scanf("%d %d",&n,&q); for(int i=1,x,y;i<n;i++) { scanf("%d %d",&x,&y); G[x].push_back(y); G[y].push_back(x); deg[x]++;deg[y]++; } int Sum=0,Tmp; for(int i=1;i<=n;i++) if(deg[i]==1) IsLeaf[i]=true,Sum++; dfs1(1,0);dfs2(1,1); Seg.Build(1,1,n); // for(int i=1;i<=n;i++) printf("%d ",Seg.Query(1,1,n,dfn[i],dfn[i]));puts(""); Tmp=Sum; while(q--) { Sum=Tmp; scanf("%d",&m); for(int i=1,x;i<=m;i++) { scanf("%d",&x); if(IsLeaf[x]) { Changed.push_back(x); IsLeaf[x]=false; continue; } V.push_back(x);Sum++; ModifyPath(x,1); } if(Sum&1) puts("-1"); else printf("%d\n",((n+m)<<1)-2-(Seg.tr[1])-m); for(int i=0;i<V.size();i++) ModifyPath(V[i],1); for(int i=0;i<Changed.size();i++) IsLeaf[Changed[i]]=true; V.clear();Changed.clear(); } return 0; }