P3513 [POI2011] KON-Conspiracy

highkj

2024-01-23 09:14:21

题解

前言

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

思路

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

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

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

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

这里我们还需判断一开始求出的那组的两个组别的人数是否都大于 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;
}