您的位置:首页 > 其它

bzoj2215: [Poi2011]Conspiracy

2016-01-14 08:47 267 查看
传送门: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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: