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