【JOISC2018|2019】【20190622】mergers

题目

(n)个节点的树,节点被分成(k)个集合,(i)属于(S_i)

一条边是可划分的当且仅当左右两边的子树不存在相同集合的点

你一次可以合并两个集合,求最少的操作次数使得所有边都不可划分

$N le 5 imes 10^5 , S_i le K le N $

题解

  • 如果(S_x=S_y),那么$ x (到) y $路径上的边都不可划分,把他们缩起来

  • 即把所有相同颜色的点两两路径的连通块缩起来

  • 得到一个所有节点颜色不同的树

  • 相当于连最少的边使得对应路径覆盖所有树边

  • 答案是(叶子数+1)/2

    #include<bits/stdc++.h>
    
    using namespace std;
    
    const int N=500010;
    
    int n,m,f[N],c[N],fa[N],dep[N],o=1,hd[N],d[N];
    struct Edge{int v,nt;}E[N<<1];
    
    char gc(){
    	static char*p1,*p2,s[1000000];
    	if(p1==p2)p2=(p1=s)+fread(s,1,1000000,stdin);
    	return(p1==p2)?EOF:*p1++;
    }
    int rd(){
    	int x=0;char C=gc();
    	while(C<'0'||C>'9')C=gc();
    	while(C>='0'&&C<='9')x=(x<<1)+(x<<3)+C-'0',C=gc();
    	return x;
    }
    
    void adde(int u,int v){
    	E[o]=(Edge){v,hd[u]};hd[u]=o++;
    	E[o]=(Edge){u,hd[v]};hd[v]=o++;
    }
    
    int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
    
    void dfs(int u){
    	f[u]=u;
    	for(int i=hd[u];i;i=E[i].nt){
    		int v=E[i].v;
    		if(v==fa[u])continue;
    		fa[v]=u;dep[v]=dep[u]+1;
    		dfs(v);
    	}
    }
    
    void merge(int u,int v){
    	u=find(u),v=find(v);
    	while(u!=v){
    		if(dep[u]<dep[v])swap(u,v);
    		f[u]=find(fa[u]),u=f[u];
    	}
    }
    
    int main(){
    	freopen("mergers.in","r",stdin);
    	freopen("mergers.out","w",stdout);
    	n=rd();m=rd();
    	for(int i=1;i<n;++i)adde(rd(),rd());
    	dfs(1);
    	for(int i=1,x;i<=n;++i){
    		x=rd();
    		if(!c[x])c[x]=i;
    		else merge(c[x],i);
    	}
    	for(int i=1;i<o;i+=2){
    		int fu=find(E[i].v),fv=find(E[i+1].v);
    		if(fu!=fv)d[fu]++,d[fv]++;
    	}
    	int lf=0;
    	for(int i=1;i<=n;++i)if(d[i]==1)lf++;
    	cout<<((lf+1)>>1)<<endl;
    	return 0;
    }
    
    
原文地址:https://www.cnblogs.com/Paul-Guderian/p/11073837.html