Codeforces Round #680 (Div. 2, based on Moscow Team Olympiad) E.Team-Building 可撤销并查集

本文介绍了一种算法,用于计算从多个组中选择两个不同的组构成二分图的方案数量。通过排除自身构成奇环的点,并使用可撤销并查集对连接不同组的边进行分类讨论,有效地解决了问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接

思路:转换题意就是求选两个不同的组构成的子图是一个二分图的方案数,正着不好做,反着,用总的减去不合法的会容易很多。
下面介绍如何找出不合法的情况:
那么可以先把一些自身就构成奇环的点找出来,这些点别的组也不能选。
接下来我们对连接不同组的边进行分类讨论,因为显然两个组构成奇环肯定环上只有两个组中的点(自身构成奇环的已经被扣掉了)。那么对每一组都用并查集来check是否构成奇环。
由于有很多不同的组,那么就用可撤销并查集来撤销上一次的merge即可。

#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
#define fi first
#define se second
#define pb push_back
#define wzh(x) cerr<<#x<<'='<<x<<endl;
int f[N],sz[N];
void init(int n){
  for(int i=1;i<=n;i++)f[i]=i,sz[i]=1;
}
int find(int x){
  while(x!=f[x]){
    x=f[x];
  }
  return x;
}
int st[N],tot;
void merge(int x,int y){
  int dx=find(x),dy=find(y);
  if(dx==dy)
    return;
  if(sz[dx]>sz[dy])swap(dx,dy);
  sz[dy]+=sz[dx];
  f[dx]=dy;
  st[++tot]=dx;
}
void roll_back(int y){
  while(tot>y){
    int now=st[tot--];
    sz[f[now]]-=sz[now];
    f[now]=now;
  }
}
int n,m,k,col[N],cant[N];
struct uzi{
  int s,t,x,y;
  bool operator <(const uzi &T)const{
    if(y==T.y)return x<T.x;
    return y<T.y;
  }
}p[N];
int cnt;
int main() {
  ios::sync_with_stdio(false);
  cin>>n>>m>>k;
  int tmp=k;
  init(2*n);
  for(int i=1;i<=n;i++)cin>>col[i];
  for(int i=1;i<=m;i++){
    int s,t;
    cin>>s>>t;
    if(col[s]==col[t]){
      if(!cant[col[s]]&&find(s)==find(t)){//出现了奇数环 说明这个点不能被选。
        cant[col[s]]=1;
        tmp--;
        continue;
      }
      merge(s,n+t);
      merge(s+n,t);
    }else{
      if(col[s]>col[t])swap(s,t);
      p[++cnt]={s,t,col[s],col[t]};
    }
  }
  int no=tot;
  sort(p+1,p+1+cnt);
  LL ans=1ll*tmp*(tmp-1)/2;
  for(int i=1,j;i<=cnt;i=j+1){
    roll_back(no);
    j=i;
    int ok=0;
    while(j+1<=cnt&&p[j+1].x==p[j].x&&p[j+1].y==p[j].y){
      j++;
    }
    if(cant[p[j].x]||cant[p[j].y])continue;
    for(int now=i;now<=j;now++){
      int l=find(p[now].s),r=find(p[now].t);
      if(l==r){
        ok=1;
        break;
      }
      merge(p[now].s,p[now].t+n);
      merge(p[now].t,p[now].s+n);
    }
    ans-=ok;
  }
  cout<<ans<<'\n';
  return 0;
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值