acm-(二分图、染色)Codeforces Round #680 (Div. 2) E. Team-Building

题面
传送门
本题其实就是问有多少组对,满足这些组的所有点形成的子图是个二分图。

直接枚举组对是 O ( n 2 ) O(n^2) O(n2)的,考虑枚举不合法的组对,首先如果同一个组的所有点形成的子图存在奇环那么这个组与其他所有组形成的子图都一定不是一个二分图。然后考虑枚举那些两个端点位于不同组的边。

我们将那些两个端点位于不同组的边分一下类,同一个类中我们让每条边的两个端点的所属组别情况都是一样的。对于同一个类的话边一定不会特别多,毕竟总边数才 500000 500000 500000,因此考虑 c h e c k check check同一个类中的边对应的两个组之间形成的子图是否合法(若不是二分图则非法)。我们可以直接给同一类中的所有边建立一个新图,对于属于同一个组的点我们不关心他们的位置关系,直接让这些点连向它们在原图中的连通分量的代表点即可(可以通过并查集实现)。而对于不同组的点我们就老实的连边即可,最终会形成一张新图,可能不是联通的。由于我们在最开始检查组内是否有奇环的时候已经给所有合法的组中的点都染色过了,利用这些染色编号,我们遍历这个新图的时候,对于不同组的边,若两端染色编号相同,并且有一个点的异或值(一个变量)还未被分配,我么你可以让这个点异或值为 1 1 1即可,这样就保证两端染色不同,不过若两个点的异或值都已经被分配,那么说明新图一定不是二分图。注意一开始要让一个初始组的异或值被分配一个值即可,异或值的主要目的是修正颜色值。

int n,m,k,fob[maxn],bl[maxn],col[maxn],tot,fa[maxn],vis[maxn],gc[maxn],vis2[maxn];
ll ans=0;
map<pi,int>mp;
vector<int>g[maxn],gv[maxn];
vector<pi >gg[maxn];
int cct=0;
bool dfs(int u,int c){
	col[u]=c;
	FOR(i,0,g[u].size()){
		int v=g[u][i];
		if(!col[v]){
			if(!dfs(v,((c-1)^1)+1))return false;
		}else if(col[v]==c){
			cct++;
			fob[bl[u]]=1;
			return false;
		}
	}
	return true;
}
int fd(int rt){
	return rt==fa[rt]?rt:(fa[rt]=fd(fa[rt]));
}
bool same(int u,int v){
	return fd(u)==fd(v);
}
void merge(int u,int v){
	if(fd(u)==fd(v))return;
	fa[fd(u)]=fd(v);
}
bool dfs2(int u){
	vis2[u]=1;
	FOR(i,0,gv[u].size()){
		int v=gv[u][i];
		if(fd(v)!=fd(u)){
			if(!gc[fd(v)]){
				if(((col[u]-1)^(gc[fd(u)]-1))+1==col[v]){
					gc[fd(v)]=2;
				}else{
					gc[fd(v)]=1;
				}
			}else{
				if(((col[u]-1)^(gc[fd(u)]-1))+1==((col[v]-1)^(gc[fd(v)]-1))+1){
					return false;
				}
			}
		}
		if(vis2[v])continue;
		if(!dfs2(v))return false;
	} 
	return true;
}
vector<int>rec;
int main(){
	n=rd(),m=rd(),k=rd();
	ans=1ll*k*(k-1)/2;
	FOR(i,1,n+1)bl[i]=rd(),fa[i]=i;
	while(m--){
		int u=rd(),v=rd();
		if(bl[u]==bl[v]){
			g[u].push_back(v);
			g[v].push_back(u);
			merge(u,v);
		}else{
			if(bl[u]>bl[v])swap(u,v);
			if(!mp[mk(bl[u],bl[v])])mp[mk(bl[u],bl[v])]=++tot;
			gg[mp[mk(bl[u],bl[v])]].push_back(mk(u,v));
		}
	}
	FOR(i,1,n+1)if(!col[i] && !fob[bl[i]])dfs(i,1);
	FOR(i,1,cct+1)ans-=k-i;
	FOR(i,1,tot+1){
		if(!gg[i].empty() && !fob[bl[gg[i][0].fi]] && !fob[bl[gg[i][0].se]]){
			FOR(j,0,gg[i].size()){
				int u=gg[i][j].fi,v=gg[i][j].se,fu=fd(u),fv=fd(v);
				if(fu>fv)swap(u,v),swap(fu,fv);
				if(!vis[u]){
					if(fu!=u){
						gv[fu].push_back(u);
						gv[u].push_back(fu);
					}
					rec.push_back(u);
					vis[u]=1;
				}
				if(!vis[v]){
					if(fv!=v){
						gv[fv].push_back(v);
						gv[v].push_back(fv);
					}
					rec.push_back(v);
					vis[v]=1;
				}
				gv[u].push_back(v);
				gv[v].push_back(u);
			}
			FOR(i,0,rec.size()){
				int u=rec[i];
				if(!vis2[u]){
					gc[fd(u)]=1;
					if(!dfs2(u)){
						ans--;
						break;
					}
				}
			}
			FOR(i,0,rec.size()){
				int u=rec[i];
				gv[fd(u)].clear();
				gc[fd(u)]=0;
				gv[u].clear();
				vis[fd(u)]=vis[u]=vis2[fd(u)]=vis2[u]=0;
			}
			rec.clear();
		}
	}
	printf("%lld\n",ans);
} /*test:
6 7 2
1 1 1 1 2 2 
1 2
2 3
1 4
3 4
4 6
3 5
5 6
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值