CF553C-Love-Triangles题解

题目传送门

题意:有一个 \(n\) 个点的无向完全图,边权为 \(0/1\),钦定 \(m\) 条边的边权,求填剩下的边的边权的方案数,满足:不存在一个三元环全为 \(0\) 或二 \(1\)\(0\)

观察第二条性质。首先显然有,如果 \(a-b,b-c\)\(1\),则 \(a-c\)\(1\),所以 \(1\) 的关系具有传递性:只要两个点在同一个 \(1\) 的连通块内,这两点之间也必然为 \(1\)。于是每个 \(1\) 的连通块必为一个边权全为 \(1\) 的完全图。于是,可以先处理钦定的边权为 \(1\) 的边,用并查集维护,对于一个钦定边权为 \(0\) 的边,如果两边连通则无解。

处理完后,一定维护出若干个由 \(1\) 连接的连通块,以及一些连接两个连通块的 \(0\) 边。连通块内部肯定边权全为 \(1\),所以只需要考虑连通块之间的。容易发现,两个连通块只要连了一条 \(1\) 的边,则合并成一个连通块,所以两个连通块之间的所有边必须全设成 \(1\)。也就是说,两个连通块之间有钦定 \(0\) 边,则一定不能合并。否则一定可以合并。最终状态一定为,合并成若干个全 \(1\) 的连通块,且这些连通块之间全为 \(0\)

研究完第二个限制,考虑第一个限制:不存在三元环全为 \(0\)。这说明连通块个数不能超过两个。于是,对于一条钦定权值为 \(0\)(即不能合并)的边,两个端点必然在不同的连通块内,相同连通块内不能出现这种边。于是抽象成二分图。

具体地,首先将全为 \(1\) 的完全图连通块缩成点,然后用钦定 \(0\) 的进行连边,则形成的图必须为二分图(否则无解)。DFS 或并查集匹配一下即可。对于方案数,每个连通块缩成的点有左右两种对称的放置方式,所以答案为 \(2^{cnt}\)。由于每种方案还有一种完全对称的方案,而我们只考虑点集分配方式,左右没有区分,所以答案再除以 \(2\)

By cxm1024

#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
struct node{int x,y,z;} a[100010];
int fa[100010];
int findf(int x) {
	if(x==fa[x]) return x;
	return fa[x]=findf(fa[x]);
}
vector<int> e[100010];
int col[100010],vis[100010];
void dfs(int now,bool color) {
	if(vis[now]&&color!=col[now]) {
		cout<<0<<endl;
		exit(0);
	} //不是二分图则无解
	if(vis[now]) return;
	vis[now]=1,col[now]=color;
	for(int v:e[now]) dfs(v,!color);
}
signed main() {
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=m;i++)
		cin>>a[i].x>>a[i].y>>a[i].z;
	for(int i=1;i<=m;i++)
		if(a[i].z==1&&findf(a[i].x)!=findf(a[i].y))
			fa[findf(a[i].x)]=findf(a[i].y);
	for(int i=1;i<=m;i++) {
		if(a[i].z==1) continue;
		if(findf(a[i].x)==findf(a[i].y)) {
			cout<<0<<endl;
			return 0;
		} // 0的两边连通则无解,否则在缩点的新图中建边
		// 这里直接把缩点的root设为了并查集的root
		e[findf(a[i].x)].push_back(findf(a[i].y));
		e[findf(a[i].y)].push_back(findf(a[i].x));
	}
	int ans=(mod+1)/2; //初始设成1/2
	for(int i=1;i<=n;i++)
		if(findf(i)==i&&!vis[i])
			dfs(i,0),(ans*=2)%=mod;
	cout<<ans<<endl;
	return 0;
}
posted @ 2023-03-01 16:02  曹轩鸣  阅读(23)  评论(0编辑  收藏  举报