[POI2011] Conspiracy 题解

\(2-SAT\) 计数里应该算是史诗级经典了。


容易想到将后勤和卧底分成两种状态,用 \(2-SAT\) 暴力建边。建边方式略去不表。

关键问题在于计数。我们似乎并没有专门的方法去进行这类计数,因此只能分析题目的关键性质了。

显然,我们容易求出一组特解,满足后勤集合为 \(S\),卧底集合为 \(T\)。考虑如下重要性质:

  • 任何一组合法解 \(S',T'\) 中,都满足不存在两个点 \(a,b\),满足 \(a,b\in S'\)\(a,b\in T\),或是 \(a,b\in T'\)\(a,b\in S\)

证明的话,假如真的存在这样一组 \(a,b\),那么他俩一定既认识也不认识,肯定不可能。

那就只剩下交换和修改一个人的组别两种将特解修改为其他解的方法。

考虑假如在 \(S\) 中有一点 \(c\),他和所有 \(T\) 中的人都是陌生的,那么只要 \(|S|>1\),它就一定可以从 \(S\) 移动到 \(T\) 中。对于 \(T\) 中点 \(d\) 也同理。同时需要注意到,我们也可以交换这样的 \(c,d\)

我们设 \(c_p\) 表示对于一个 后勤/卧底 \(p\),卧底/后勤 中有几个人与它的关系是 认识/陌生,同时称这类人是对于 \(p\) 的“诡异人”。那么上述的 \(c,d\) 这类点就都满足 \(c_p=0\)

考虑若一个点 \(c_p>1\),那么这个点肯定不可能和另一个集合中的点交换,或移动到另一个集合。

若一个点 \(c_p=1\),且他的诡异人 \(q\) 满足 \(c_q=0\),那么他们显然可以交换。除此以外,\(p\) 都无法移动。

时间复杂度 \(O(n^2)\)

#include<bits/stdc++.h>
using namespace std;
const int N=5005,M=10005;
int n,a[N][N],id,tp,cnt,pe[N];vector<short>g[M];
int dfn[M],low[M],st[M],vs[M],idx[M],c[N],to[N];
int sum,hq,ti,pq,mq,ans=1;
void tarjan(int x){
	dfn[x]=low[x]=++id,vs[st[++tp]=x]=1;
	for(auto y:g[x]){
		if(!dfn[y]) tarjan(y),low[x]=min(low[x],low[y]);
		else if(vs[y]) low[x]=min(low[x],dfn[y]);
	}if(dfn[x]!=low[x]) return;cnt++;
	while(st[tp+1]!=x) idx[st[tp]]=cnt,vs[st[tp--]]=0;
}int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0),cin>>n;
	for(int i=1;i<=n;i++){
		int k,x;cin>>k,sum+=k;
		while(k--) cin>>x,a[i][x]=1;
	}if(!sum||sum==n*(n-1)) return cout<<n,0;
	for(int i=1;i<=n;i++) for(int j=1;j<=n;j++)
		if(i^j) (a[i][j]?g[i+n].push_back(j):g[i].push_back(j+n));
	for(int i=1;i<=2*n;i++) if(!dfn[i]) tarjan(i);
	for(int i=1;i<=n;i++) if(idx[i]==idx[n+i]) return cout<<0,0;
	for(int i=1;i<=n;i++) pe[i]=(idx[i]<idx[n+i]),(pe[i]?hq++:ti++);
	for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(i^j)
		if((pe[i]&&!pe[j]&&a[i][j])||(!pe[i]&&pe[j]&&!a[i][j])) c[i]++,to[i]=j;
	for(int i=1;i<=n;i++){
		if(c[i]==1&&!c[to[i]]) ans++;
		if(!c[i]) (pe[i]?pq++:mq++);
	}cout<<ans+(hq>1)*pq+(ti>1)*mq+pq*mq;
	return 0;
}
posted @ 2025-07-01 16:22  长安一片月_22  阅读(13)  评论(0)    收藏  举报