P3513 [POI2011] KON-Conspiracy

前言

这是一道神仙题,思路挺巧妙的。

思路

我们首先可以发现这是一个 2-SAT,那么我们先想如何建模,首先如果两个人是好友关系(后面用 \(i\)\(j\) 表示),那么如果第 \(i\) 个人进了同谋者则 \(j\) 必须进后勤组,如果两人不是好友关系则第 \(i\) 个人进后勤组第 \(j\) 个人必须进同谋组,然后就跑一遍 2-SAT 模板即可,然后就可以判无解了。

但是,这道题要求求出方案数,所以我们先要得出一个结论,对于一种可行解一定不能将同一组的两个及以上的人拿到另一组去(这个应该一看就懂,就不证明了)。

那么我们就能得到 \(3\) 种转换方式分别是:从两组中任选一组拿一人到另外一组以及从两组中分别选一人进行交换。

然后我们就可以先预处理出 \(id_i\) 以及 \(cnt_i\) 分别表示当 \(i\) 拿到另一组后会与 \(i\) 出现矛盾的点的下标和 \(i\) 到另一组后会出现矛盾的点的数量,然后我们再对三种情况进行讨论。

  • 如果是将 \(i\) 单独拿到另一组,那么我们首先要满足 \(i\) 拿过去后不会出现矛盾即 \(cnt_i\) 等于 \(0\),这里需要判断拿过去后当前组是否还有人。

  • 当要将 \(i\) 与某个点交换时且 \(cnt_i\) 等于 \(0\),那么我们就只能交换那些换过去也不会出现矛盾的点(即 \(cnt_j\) 也等于 \(0\))。

  • 当要将 \(i\) 与某个点交换时且 \(cnt_i\) 等于 \(1\),那么我们就可以拿出与 \(i\) 矛盾的那一个点,如果 \(cnt_{id_i}\) 等于 \(0\) 时才可以交换。

  • 当要将 \(i\) 与某个点交换时且 \(cnt_i>1\) 时我们无法进行任何交换因为交换一下只能抵消掉一对矛盾,所以无论如何交换都会剩有矛盾。

这里我们还需判断一开始求出的那组的两个组别的人数是否都大于 \(1\),如果满足则答案初始化为 \(1\) 否则为 \(0\)

注意这道题的空间给得有点小。

代码

#include <bits/stdc++.h>
using namespace std ;
#define rep(i,x,y) for(int i=x;i<=y;i++)
#define rep1(i,x,y) for(int i=x;i>=y;i--)
#define fire signed
#define kong putchar(' ')
#define end putchar('\n')
#define in(x) scanf("%d",&x)
#define lcm(x,y) x*y/__gcd(x,y) 
#define il inline
#define pb push_back
il void print(int x) {
	if(x>=10) print(x/10);
	putchar(x%10+'0');
}
const int N=5010;
int mp[N][N];
vector<int>v[N<<1];
int dfn[N<<1],low[N<<1],idx;
int is[N<<1],co[N<<1],cnt,n;
int id[N],_cnt[N];
stack<int>s;
il void dfs(int x) {//模板 
	dfn[x]=low[x]=++idx;
	s.push(x);
	is[x]=1;
	for(auto to:v[x]) {
		if(!dfn[to]) dfs(to),low[x]=min(low[x],low[to]);
		else if(is[to]) low[x]=min(low[x],dfn[to]);
	}
	if(low[x]==dfn[x]) {
		int p;
		cnt++;
		do{
			p=s.top();
			s.pop();
			is[p]=false;
			co[p]=cnt;
		}while(p!=x);
	}
}
/*
1~n 合作
n+1~2*n 不认识 
*/
int res;
vector<int>a,b;
fire main() {
	in(n);
	rep(i,1,n) {
		int w;
		in(w);
		rep(j,1,w) {
			int x;
			in(x);
			mp[i][x]=1;
			v[i+n].pb(x);//连边 
		}
		rep(j,i+1,n) if(!mp[i][j]) v[i].pb(j+n),v[j].pb(i+n);//连边 
	}
	rep(i,1,2*n) if(!dfn[i]) dfs(i);
	rep(i,1,n) {
		if(co[i]==co[i+n]) {
			cout<<"0";//无解 
			return false;
		}
		if(co[i]<co[i+n]) a.pb(i);//找出这一组中两组成员 
		else b.pb(i);
	}
	if(a.size()&&b.size()) res=1;
	for(auto x:a) {
		for(auto y:b) {
			if(mp[x][y]) id[x]=y,_cnt[x]++;//预处理矛盾 
			else id[y]=x,_cnt[y]++;
		}
	}
	int tot=false,_tot=false;
	for(auto y:b) if(!_cnt[y]) tot++; //预处理 
	for(auto x:a) if(!_cnt[x]) _tot++;
	for(auto x:a) {
		if(!_cnt[x]) res+=(a.size()>1?1:0)+tot;//分类讨论 
		else if(_cnt[x]==1){
			int y=id[x];
			if(!_cnt[y]) res++;
		}
	}
	for(auto y:b) {
		if(!_cnt[y]) res+=(b.size()>1?1:0)+_tot;
		else if(_cnt[y]==1){
			int x=id[y];
			if(!_cnt[x]) res++;
		}
	}
	printf("%d\n",res);
	return false;
}
posted @ 2024-01-31 11:36  highkj  阅读(10)  评论(0)    收藏  举报