YbtOJ-染色计划【树链剖分,线段树,tarjan】
正題
題目大意
給出nnn個(gè)點(diǎn)的一棵樹(shù),每個(gè)點(diǎn)有個(gè)顏色aia_iai?,你每次可以選擇一個(gè)顏色全部變成另一個(gè)顏色。
求最少多少次操作可以把一種顏色變成一個(gè)完整的連通塊。
1≤k≤n≤2×1051\leq k\leq n\leq 2\times 10^51≤k≤n≤2×105
解題思路
考慮如果我們要把一個(gè)顏色變成一個(gè)聯(lián)通塊,那么首先得把它目前包含它顏色點(diǎn)的最小聯(lián)通子圖全都同化,并且同化這些顏色之后還有可能需要同化其他更多顏色。
這是一個(gè)類(lèi)似于跑圖的過(guò)程,我們可以考慮建邊,如果顏色AAA需要顏色BBB那么A→BA\rightarrow BA→B,最后跑出來(lái)的圖我們找一個(gè)點(diǎn)能走到的點(diǎn)數(shù)最少即可。
至于怎么優(yōu)化這個(gè)建圖的過(guò)程,我們樹(shù)鏈剖分+線(xiàn)段樹(shù)上維護(hù)每個(gè)節(jié)點(diǎn),然后每個(gè)節(jié)點(diǎn)連向?qū)?yīng)的顏色。
然后對(duì)于一種顏色我們按照點(diǎn)的dfsdfsdfs序排序,然后收尾相連相鄰的點(diǎn)之間的路徑拼起來(lái)恰好是它顏色的生成子圖的兩倍,之間用樹(shù)鏈剖分去連邊就好了。
至于建好圖之后跑個(gè)tarjan就能求出答案了
時(shí)間復(fù)雜度:O(nlog?2n)O(n\log^2 n)O(nlog2n)
code
#include<cstdio> #include<cstring> #include<algorithm> #include<vector> #include<stack> using namespace std; const int N=2e5+10,M=N*5; struct node{int to,next; }a[N<<1]; int n,k,cnt,tot,c[N],ls[N],fa[N],siz[N]; int seg[N],id[N],top[N],son[N],dep[N]; int num,dfc,p[M],dfn[M],low[M],col[M],f[M],in[M]; bool ins[N];stack<int> s;vector<int> v[N],G[M]; void addl(int x,int y){a[++tot].to=y;a[tot].next=ls[x];ls[x]=tot;return; } void dfs1(int x){dep[x]=dep[fa[x]]+1;siz[x]=1;for(int i=ls[x];i;i=a[i].next){int y=a[i].to;if(y==fa[x])continue;fa[y]=x;dfs1(y);siz[x]+=siz[y];if(siz[y]>siz[son[x]])son[x]=y;}return; } void dfs2(int x){seg[++cnt]=x;id[x]=cnt;if(son[x]){top[son[x]]=top[x];dfs2(son[x]);}for(int i=ls[x];i;i=a[i].next){int y=a[i].to;if(y==fa[x]||y==son[x])continue;top[y]=y;dfs2(y);}return; } void Build(int x,int L,int R){p[x]=++cnt;if(L==R){G[p[x]].push_back(c[seg[L]]);return;}int mid=(L+R)>>1;Build(x*2,L,mid);Build(x*2+1,mid+1,R);G[p[x]].push_back(p[x*2]);G[p[x]].push_back(p[x*2+1]);return; } void Change(int x,int L,int R,int l,int r,int vp){if(L==l&&R==r){G[vp].push_back(p[x]);return;}int mid=(L+R)>>1;if(r<=mid)Change(x*2,L,mid,l,r,vp);else if(l>mid)Change(x*2+1,mid+1,R,l,r,vp);else Change(x*2,L,mid,l,mid,vp),Change(x*2+1,mid+1,R,mid+1,r,vp);return; } void Recovery(int x,int y,int s){ // printf("%d %d %d\n",x,y,s);while(top[x]!=top[y]){if(dep[top[x]]<dep[top[y]])swap(x,y);Change(1,1,n,id[top[x]],id[x],s);x=fa[top[x]];}if(dep[x]>dep[y])swap(x,y);Change(1,1,n,id[x],id[y],s);return; } void tarjan(int x){dfn[x]=low[x]=++dfc;ins[x]=1;s.push(x);for(int i=0;i<G[x].size();i++){int y=G[x][i];if(!dfn[y]){tarjan(y);low[x]=min(low[x],low[y]);}else if(ins[y])low[x]=min(low[x],dfn[y]);}if(low[x]==dfn[x]){int y;++num;do{y=s.top();s.pop();ins[y]=0;f[num]+=(y<=k);col[y]=num;}while(y!=x);}return; } bool cmp(int x,int y) {return id[x]<id[y];} int main() {freopen("color.in","r",stdin);freopen("color.out","w",stdout);scanf("%d%d",&n,&k);for(int i=1;i<n;i++){int x,y;scanf("%d%d",&x,&y);addl(x,y);addl(y,x);}for(int i=1;i<=n;i++)scanf("%d",&c[i]),v[c[i]].push_back(i);dfs1(1);top[1]=1;dfs2(1);cnt=k;Build(1,1,n);for(int i=1;i<=k;i++){if(v[i].size()<2)continue;sort(v[i].begin(),v[i].end(),cmp);for(int j=1;j<v[i].size();j++)Recovery(v[i][j-1],v[i][j],i);}for(int i=1;i<=cnt;i++)if(!dfn[i])tarjan(i);for(int x=1;x<=cnt;x++){for(int i=0;i<G[x].size();i++){int y=G[x][i];if(col[x]==col[y])continue;in[col[x]]++;}}int ans=k;for(int i=1;i<=k;i++)if(!in[col[i]])ans=min(ans,f[col[i]]);printf("%d\n",ans-1);return 0; }總結(jié)
以上是生活随笔為你收集整理的YbtOJ-染色计划【树链剖分,线段树,tarjan】的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 如何编辑排版WORD文档word文档怎么
- 下一篇: CF603E-Pastoral Oddi