2-SAT

前置芝士:强连通分量。

先放一个板子题:2-SAT

我们先考虑拆点,把每个变量 \(i\) 拆成两个点,\(i\times 2\)\(i\times 2 + 1\),前一个代表这个变量 \(i\) 取假,后一个代表这个变量 \(i\) 取真。

既然有了点,我们就要考虑连边。例如给一个条件:\(1\)\(1\) 或者 \(3\)\(0\)。我们就要建两条边,一条为如果 \(1\)\(0\)\(3\)\(0\),即 \(2\rightarrow 6\);另一条为如果 \(3\)\(1\)\(1\)\(1\),即 \(7\rightarrow 3\)

然后看一下这条边表示什么,表示的是一个要求是另一个要求的充分条件(建议学习一下充分条件与必要条件)。

那么,如果发现图中出现了 \(i\) 真是 \(i\) 假的充分条件;\(i\) 假是 \(i\) 真的充分条件,那么一定无解,因为不管 \(i\) 取什么,条件1总会要求你取 \(i\) 当前的值取反,这显然不可能成立。

换句话说,就是 \(\forall i\in[1,n]\) 如果 \(i\times 2\)\(i\times 2 + 1\) 在一个强连通分量中,一定无解,否则有解。

说了这么多,我们终于说完了怎么判断有没有解,接下来说一下怎么构造解。

首先我们记 \(id_i\) 表示图中的 \(i\) 号点所在的强连通分量的编号。根据上面的描述,如果 \(\exists i\in [1,n],id_{i\times 2}=id_{i\times 2 + 1}\),无解。

否则这两个东西的 \(id\) 数组,一定一个大一个小(废话)。

那么,\(id\) 更小的那一部分可以认为是另一部分的上司(不同于寻常意义),换句话说它具有决定权,变量的值就取决于他。

这是一个小结论,证明的话建议看一些其他资料,这里建议记住,遇到的时候直接用就可以了。

下面放一下板子题的代码:

#include<bits/stdc++.h>
#define int long long
#define N 2000005
#define M 2000005
using namespace std;
int n,m,h[N],e[M],ne[M],idx,dfn[N],low[N],ts,stk[N],top,id[N],cnt;
bool ins[N];
void add(int a,int b){//链式前向星建边 
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx++;
}
void tar(int u){//经典tarjan求强连通分量 
    dfn[u]=low[u]=++ts;
    stk[++top]=u;
    ins[u]=1;
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i];
        if(!dfn[j]){
            tar(j);
            low[u]=min(low[u],low[j]);
        }
        else if(ins[j])low[u]=min(low[u],dfn[j]);
    }
    if(low[u]==dfn[u]){
        int y;
        cnt++;
        do{
            y=stk[top--];
            ins[y]=0;
            id[y]=cnt;
        }while(y!=u);
    }
}
signed main(){
    cin>>n>>m;
    memset(h,-1,sizeof h);
    while(m--){
        int i,a,j,b;
        cin>>i>>a>>j>>b;
        i--;j--;
        add(2*i+!a,2*j+b);//按照上面说的充分条件的方式建边 
        add(2*j+!b,2*i+a);
    }
    for(int i=0;i<n*2;i++){
        if(!dfn[i]){//tarjan 
            tar(i);
        }
    }
    for(int i=0;i<n;i++){
        if(id[i*2]==id[i*2+1]){//互为充分条件则无解 
            cout<<"IMPOSSIBLE";
            return 0;
        }
    }
    cout<<"POSSIBLE\n";//否则有解 
    for(int i=0;i<n;i++){
        if(id[i*2]<id[i*2+1]){//"上司"具有最终决定权 
            cout<<"1 ";
        }
        else cout<<"0 ";
    }
    return 0;
}

再来一个稍微困难的题:满汉全席

先把原题面进行转化,发现对于每个评审员要满足两个要求的至少一个。所以可以认为 \(m\) 代表假,\(h\) 代表真,这就转化成了上一道板子题。

我们对 \(m\)\(h\) 进行分类讨论,每个要求建 \(2\) 条边,最后判断代表一个变量的两个点在不在一个强连通分量中即可。

这里把这道题进行略微强化,请读者思考如果 \(2\) 个要求能且仅能满足 \(1\) 个该如何做。

posted @ 2024-07-10 21:32  zxh923  阅读(5)  评论(0编辑  收藏  举报