zoukankan      html  css  js  c++  java
  • 【知识点】2-sat

    2-sat问题:

    给定$n$个二元组$(A,B)$,你需要从这些二元组中选取$n$个元素,每个二元组中必须恰好选择一个元素。

    同时给出$m$个约束条件,每个条件形如“选A必须选B”、“选A就不能选B”等。求一种合法的选取方案。

    思路:

    考虑将所有约束条件转化成“选A必须选B”这种类型。

    建立一个有$2 imes n$个节点的图,对于每个条件我们连一条$A ightarrow B$的有向边。

    此时这张图满足一些性质:

    1. 如果存在一个二元组$(A,B)$,使得A可达B且B可达A(即A、B在同一个强连通分量中),那么肯定无解。若不存在这样的二元组那么一定有解。
    2. 若一个点被选入答案序列,那么这个点所有的可达节点都必须被选入。
    3. 若一个点确定不被选入答案序列,那么所有可达这个点的节点都不能被选入。

    基于后两个性质,我们考虑先将原图缩点(保证无环)。然后建立原图的反图。

    按照反图的拓扑序依次选择节点并标记为选,同时将与该节点在同一二元组内的点标记为不选,将不选标记沿着路径传递。

    该方法的证明见此:赵爽《2-SAT解法浅析》

    但实际上同一二元组内的两个点会在缩点后的DAG上直接或间接相连,而Tarjan求的强连通分量的顺序又恰好是反拓扑序。

    所以直接Tarjan之后枚举所有二元组,选择拓扑序较大的那个即可。用反证法易证其正确性。

    模板题目:loj10097

    #include<algorithm>
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<stack>
    
    using namespace std;
    #define MAXN 100005
    #define MAXM 500005
    #define INF 0x7fffffff
    #define ll long long
    
    inline int read(){
        int x=0,f=1;
        char c=getchar();
        for(;!isdigit(c);c=getchar())
            if(c=='-')
                f=-1;
        for(;isdigit(c);c=getchar())
            x=x*10+c-'0';
        return x*f;
    }
    
    int N,M,hd[MAXN],to[MAXM],nxt[MAXM],cnt;
    int dfn[MAXN],low[MAXN],cl[MAXN],tot,num;
    bool vis[MAXN]; stack<int> s;
    
    inline void addedge(int u,int v) {to[++cnt]=v,nxt[cnt]=hd[u],hd[u]=cnt;}
    
    inline void tarjan(int u){
        dfn[u]=low[u]=++num;
        vis[u]=1;s.push(u);
        for(int i=hd[u];i;i=nxt[i]){
            int v=to[i];
            if(!dfn[v])
                tarjan(v),low[u]=min(low[u],low[v]);
            else if(vis[v])
                low[u]=min(low[u],dfn[v]);
        }
        if(dfn[u]==low[u]){
            tot++;
            while(s.top()!=u){
                cl[s.top()]=tot;
                vis[s.top()]=0;
                s.pop();
            }
            cl[s.top()]=tot;
            vis[s.top()]=0;
            s.pop();
        }
    }
    
    int main(){
        N=read()<<1,M=read();
        for(int i=1;i<=M;i++){
            int u=read(),v=read();
            addedge(u,(v&1)?v+1:v-1);
            addedge(v,(u&1)?u+1:u-1);
        }
        for(int i=1;i<=N;i++)
            if(!dfn[i])
                tarjan(i);
        for(int i=1;i<=N;i+=2)
            if(cl[i]==cl[i+1]){
                printf("NIE
    ");
                return 0;
            }
        for(int i=1;i<=N;i+=2)
            if(cl[i]<cl[i+1]) printf("%d
    ",i);
            else printf("%d
    ",i+1);
        return 0;
    }
  • 相关阅读:
    设计模式
    DOS批处理脚本
    BAT 批处理脚本 教程
    spring4配置文件详解
    软件过程
    error C2440 “static_cast” 无法从“void (__thiscall C* )(void)...
    error C2065: “IDD_DIALOG1”: 未声明的标识符
    在另一个编辑器中打开
    Github 结合 Hexo 搭建轻量博客
    收藏---wordpress搭建出来的blog
  • 原文地址:https://www.cnblogs.com/YSFAC/p/10444078.html
Copyright © 2011-2022 走看看