highkj
2024-01-23 09:14:21
这是一道神仙题,思路挺巧妙的。
我们首先可以发现这是一个 2-SAT,那么我们先想如何建模,首先如果两个人是好友关系(后面用
但是,这道题要求求出方案数,所以我们先要得出一个结论,对于一种可行解一定不能将同一组的两个及以上的人拿到另一组去(这个应该一看就懂,就不证明了)。
那么我们就能得到
然后我们就可以先预处理出
如果是将
当要将
当要将
当要将
这里我们还需判断一开始求出的那组的两个组别的人数是否都大于
注意这道题的空间给得有点小。
#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;
}