LOJ#2155. 「POI2011 R1」同谋者 Conspiracy

题目描述
题解:

这个题目要求的是把一张无向图变成一个团和一个独立集的方案数。这看似好像无从下手。那么我们可以换一个角度思考,我们考虑先求出一个解,然后通过调整得出所有解。

假设已经求出了一个解,我们会发现,其它所有的解只可能由这个解通过三种方式得到:
1.将一个原本在独立集里面的点放到团中(可行的条件下)。
2.将一个原本在团中的点放到独立集中(可行的条件下)。
3.将一个独立集中的点和团中的点交换(可行的条件下)。
意思就是,如果有属于同一组中的两个及以上的点到另一个组中,就肯定不行。

那么接下来问题就转化成了怎样求一组解。我们可以把一个点拆分成两个状态,放入团或是放入独立集中。那么我们的目标即是将这2n个状态分到两个集合中,每个集合中有n个状态,此时我们随便选择一个集合,就是一组可行解了。
那么我们就可以用2-Sat来解决。
约束条件是如果a到b有边,那么a在独立集中,b就不能在。
如果a到b无边,那么a在团中,b就不能在。
剩下的过程就是Tarjan缩点加上建反图拓扑排序一下。

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int maxn=10005;
int n;
namespace twosat{
	vector<int> e[maxn],a[maxn],f[maxn];
	bool vis[maxn];
	bitset<5005> hsh[5005];
	int tot,top,ans,inn[maxn],c[maxn],dfn[maxn],low[maxn],gr[maxn],stack[maxn],que[maxn];
	bool pic[maxn],pd[maxn],pd1[maxn];
	void nosolu(){printf("0\n"); exit(0);}
	void init(){
		scanf("%d",&n);
		for (int i=1;i<=n;i++){
			int x,y; scanf("%d",&x);
			for (int j=1;j<=x;j++) scanf("%d",&y),hsh[i][y]=1;
			for (int j=1;j<=n;j++)
			if (j!=i){
				if (hsh[i][j]) e[2*i].push_back(2*j-1);//2*i-1是团,2×i是独立集
				else e[2*i-1].push_back(2*j);	
			}
		}
		if (n==2) {printf("2\n"); exit(0);}
	}
	void tarjan(int x){
		dfn[x]=low[x]=++tot,vis[x]=1,stack[++top]=x;
		for (int j=0;j<e[x].size();j++)
		if (!dfn[e[x][j]]) {
			int y=e[x][j];
			tarjan(y);
			low[x]=min(low[x],low[y]);
		} else if (vis[e[x][j]]) low[x]=min(low[x],dfn[e[x][j]]);
		if (low[x]==dfn[x]){
			while (stack[top]!=x) {gr[stack[top]]=x,vis[stack[top]]=0,top--;}
			gr[stack[top]]=x,vis[stack[top]]=0,top--;
		}
	}
	void tar(){for (int i=1;i<=2*n;i++) if (!dfn[i]) tarjan(i);}
	int get(int x){if (x%2==1) return x+1; else return x-1;}
	void rebuild(){
		for (int i=1;i<=2*n;i++) f[gr[i]].push_back(i);
		for (int i=1;i<=n;i++) if (gr[2*i-1]==gr[2*i]) nosolu();
		for (int i=1;i<=2*n;i++){
			for (int j=0;j<e[i].size();j++)
			if (gr[i]!=gr[e[i][j]]) a[gr[e[i][j]]].push_back(gr[i]),inn[gr[i]]++; 
			e[i].clear();
		}
	}
	void topsort(){
		memset(vis,0,sizeof(vis));
		int head=0,tail=0;
		for (int i=1;i<=2*n;i++)
		if (!inn[i]) que[++tail]=i;
		while (head!=tail){
			head++; int u=que[head];
			for (int i=0;i<f[u].size();i++)
			if (!vis[get(f[u][i])]) pic[f[u][i]]=1,vis[f[u][i]]=1;
			f[u].clear();
			for (int i=0;i<a[u].size();i++)
			if (inn[a[u][i]]){
				int v=a[u][i]; inn[v]--;
				if (!inn[v]) que[++tail]=v;
			}
			a[u].clear();
		}
	}
	void calc(){
		int ans=0,sum1=0,sum2=0;
		for (int i=1;i<=n;i++)
		if (pic[2*i-1]) c[i]=1,sum1++; else c[i]=2,sum2++; //1是团,2是独立集
		if (sum1&&sum2) ans++;
		for (int i=1;i<=n;i++)
		if (c[i]==1){
			bool check=0;
			for (int j=1;j<=n;j++)
			if (hsh[i][j]&&c[j]==2) {check=1; break;}
			if (check==0&&(sum1-1)) ans++,pd[i]=1;
		} else {
			bool check=0;
			for (int j=1;j<=n;j++)
			if (c[j]==1&&(!hsh[i][j])) {check=1; break;}
			if (check==0&&(sum2-1)) ans++,pd1[i]=1;
		}
		for (int i=1;i<n;i++)
		for (int j=i+1;j<=n;j++)
		if (((pd[i]&&pd1[j])||(pd1[i]&&pd[j]))&&hsh[i][j])
		ans++;
		printf("%d\n",ans);
	}
	void solve(){
		init(); tar(); rebuild(); topsort(); calc();
	}
}
int main(){
	twosat::solve();
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值