Jzoj4437 线性代数与逻辑

不要被标题迷惑了,这是个图论题目。

我们发现,若Ai,j=1那么显然Xi,j=1,所以y[i]!=y[j]

这时候就变成了一个类似二分图的图,若Ai,j=1我们将节点i,j连一条边表示i和j不能相等

我们先对每个节点i拆点成i和i+n,跑一次2sat,若i和i+n可以互达,那么显然无解,否则我们要将所有的点分入两个集合并且使得两个集合大小的乘积尽可能大

这时候就可以对于每个节点i,dfs一次,将这个节点i加入集合0或者集合1中元素较少那个,那么显然,与i连边的所有的j都要加入另一个,而所有与j相连的节点k。。。以此类推,直到每个节点都被分入集合0或者集合1

为什么上述方法是可行的?因为如果我们发现这个图是一个二分图,那么显然一次分配过程中,分配进入集合0和集合1的点的个数差不会超过一,所以集合0和集合1

那么答案就是两个集合大小的乘积

#include<stdio.h>
#include<string.h>
#include<vector>
using namespace std;
vector<int> g[2010];
int n=0,T,st[2010],c[3]; bool vis[2010],A[1010][1010];
inline int d(int x){ return x<=n?x+n:x-n; }
int dfs(int x){
	vis[x]=st[x]=1;
	if(st[d(x)]) return st[x]=0;
	for(int i=0,z=g[x].size();i<z;++i)
		if(!vis[g[x][i]])
		if(!dfs(g[x][i])) return 0;
	st[x]=0; return 1;
}
void dijk(int x){
	for(int i=0,v,z=g[x].size();i<z;++i){
		v=(g[x][i]-1)%n+1;
		if(!st[v]){ 
			c[st[v]=st[x]^3]++;
			dijk(v);
		}
	}
}
void Init(){
	memset(vis,0,sizeof vis);
	for(int i=1;i<2010;++i) g[i].clear();
	c[1]=c[2]=0; memset(st,0,sizeof st);
}
int kmp(){
	scanf("%d",&n);
	for(int x,i=1;i<=n;++i)
		for(int j=1;j<=n;++j){
			scanf("%d",A[i]+j);
			if(A[i][j]){
				g[i].push_back(j+n);
				g[i+n].push_back(j);
			}
		}
	for(int i=1;i<=n<<1;++i) //检查是否有解
		if(!vis[i]&& !dfs(i)){
			puts("-1"); return 0;
		}
	for(int i=1;i<=n;++i)//分配点,st[i]=1代表i在集合0,st[i]=2代表在集合1,st[i]=0代表未分配
 		if(!st[i]){
			if(c[1]>c[2]) st[i]=2; else st[i]=1;
			c[st[i]]++; dijk(i);
		}
	int ans=0;
	for(int i=1;i<=n;++i) --st[i];
	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j)
			if(!A[i][j]&&st[i]^st[j]) ans++;
	printf("%d
",ans);
}
int main(){ for(scanf("%d",&T);T--;Init()) kmp(); }

原文地址:https://www.cnblogs.com/Extended-Ash/p/9477326.html