YbtOJ-染色计划【树链剖分,线段树,tarjan】


正题


题目大意

给出\(n\)个点的一棵树,每个点有个颜色\(a_i\),你每次可以选择一个颜色全部变成另一个颜色。

求最少多少次操作可以把一种颜色变成一个完整的连通块。

\(1\leq k\leq n\leq 2\times 10^5\)


解题思路

考虑如果我们要把一个颜色变成一个联通块,那么首先得把它目前包含它颜色点的最小联通子图全都同化,并且同化这些颜色之后还有可能需要同化其他更多颜色。

这是一个类似于跑图的过程,我们可以考虑建边,如果颜色\(A\)需要颜色\(B\)那么\(A\rightarrow B\),最后跑出来的图我们找一个点能走到的点数最少即可。

至于怎么优化这个建图的过程,我们树链剖分+线段树上维护每个节点,然后每个节点连向对应的颜色。

然后对于一种颜色我们按照点的\(dfs\)序排序,然后收尾相连相邻的点之间的路径拼起来恰好是它颜色的生成子图的两倍,之间用树链剖分去连边就好了。

至于建好图之后跑个tarjan就能求出答案了

时间复杂度:\(O(n\log^2 n)\)


code

#include
#include
#include
#include
#include
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 s;vector 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[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