【JOISC2018|2019】【20190622】mergers

题目

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

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

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

$N \le 5\times 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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值