bzoj2215: [Poi2011]Conspiracy

传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=2215

思路:一道很好的2-sat题

首先一个人要么分配给同谋者,要么分配给后勤组织

这可以考虑2-sat

那么怎么连边?这个很显然

如果(i,j)有边,那么一个在同谋者,则另一个必不在同谋者

如果(i,j)无边,那么一个在后勤组织,另一个必不在后勤组织


然后考虑求方案数

先求出一种方案

然后我们就会发现一个性质

只会有一个人被换过去,或者换过来

因为两个后勤组织的人一定有边,不可能同时换过去

同理因为两个同谋者的人一定无边,不可能同时换过去


于是就可以愉(keng)快(die)地分类讨论了


首先有三种情况

1:从后勤组织过去一个人

2:从同谋者过去一个人

3:两个组织互换一个人


先预处理出每个人到另一个组织后会有几个人和他冲突,如果只有一个,和他冲突的是谁

对于过去一个人的情况

只要无人与他冲突并且过去后还有人即可方案数+1


对于交换的情况

1.如果i有一个冲突的人j,那么如果j也只有一个冲突的人并且j冲突的人是i,那么方案数+1,但为了不重复,只在i<j时统计

2.如果i有一个冲突的人j,j没有冲突的人,那么方案数+1,这种不会重复

3.如果i没有冲突的人,那么它和另一个团体中任意一个也没有冲突的人可以互换

记录后勤组织交换后无冲突的人数t0,同谋者交换后无冲突的人数t1

方案数+=t1*t2


然后就没有然后了



#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
const int maxn=10010,maxm=50000010;
using namespace std;
int n,low[maxn],dfn[maxn],tim,sta[maxn],top,bel[maxn],bcnt,cnt1,cnt0,boom[maxn],ans[maxn];bool g[5010][5010],ins[maxn];
int pre[maxm],now[maxn],son[maxm],tot,cnt[maxn],res;char ch;
int p0(int x){return x<<1;}
int p1(int x){return (x<<1)|1;}
void read(int &x){
	for (ch=getchar();!isdigit(ch);ch=getchar());
	for (x=0;isdigit(ch);ch=getchar()) x=x*10+ch-'0';
}
//0是后勤(有边) 1是同谋者(无边)
void add(int a,int b){pre[++tot]=now[a],now[a]=tot,son[tot]=b;}
void tarjan(int x){
	dfn[x]=low[x]=++tim,ins[x]=1,sta[++top]=x;
	for (int y=now[x];y;y=pre[y]){
		if (!dfn[son[y]]) tarjan(son[y]),low[x]=min(low[x],low[son[y]]);
		else if (ins[son[y]]) low[x]=min(low[x],dfn[son[y]]);
	}
	if (dfn[x]==low[x]){
		int xx;bcnt++;
		do{xx=sta[top--],ins[xx]=0,bel[xx]=bcnt;}while (x!=xx); 
	}
}

void init(){
	scanf("%d",&n);
	for (int i=1,k;i<=n;i++){
		read(k);
		for (int j=1,x;j<=k;j++) read(x),g[i][x]=g[x][i]=1;
	}
	//for (int i=1;i<=n;i++,puts("")) for (int j=1;j<=n;j++) printf("%d ",g[i][j]);
	for (int a=1;a<=n;a++) for (int b=1;b<a;b++){
		if (g[a][b]) add(p1(a),p0(b)),add(p1(b),p0(a));
		else add(p0(a),p1(b)),add(p0(b),p1(a));
	}
}

void work(){
	for (int i=2;i<=p1(n);i++) if (!dfn[i]) tarjan(i);
	for (int i=1;i<=n;i++) if (bel[p0(i)]==bel[p1(i)]) return;
	for (int i=1;i<=n;i++){
		ans[i]=(bel[p0(i)]>bel[p1(i)]);
		if (ans[i]) cnt1++;else cnt0++;
	}
	//for (int i=1;i<=n;i++) printf("%d %d\n",i,ans[i]);
	//if (!cnt0||!cnt1) return;
	res=(cnt0&&cnt1);int t0=0,t1=0;
	for (int i=1;i<=n;i++){
		if (ans[i]){
			for (int j=1;j<=n;j++) if (i!=j&&!ans[j]&&!g[i][j])
				cnt[i]++,boom[i]=j;
		}
		else{
			for (int j=1;j<=n;j++) if (i!=j&&ans[j]&&g[i][j])
				cnt[i]++,boom[i]=j;
		}
	}
	//for (int i=1;i<=n;i++) printf("%d %d %d %d\n",i,ans[i],cnt[i],boom[i]);
	for (int i=1;i<=n;i++) if (!ans[i]){
		if (!cnt[i]&&cnt0>1) res++;
		if (!cnt[i]) t0++;
		if (cnt[i]==1)
			if ((boom[boom[i]]==i&&cnt[boom[i]]==1&&i<boom[i])||!cnt[boom[i]]) res++;
	}
	for (int i=1;i<=n;i++) if (ans[i]){
		if (!cnt[i]&&cnt1>1) res++;
		if (!cnt[i]) t1++;
		if (cnt[i]==1)
			if ((boom[boom[i]]==i&&cnt[boom[i]]==1&&i<boom[i])||!cnt[boom[i]]) res++;
	}
	res+=t0*t1;
}

int main(){
	init(),work(),printf("%d\n",res);
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值