• 【整理】2-SAT


    2-satisfiability,我们一般将其缩写为 2-sat。

    了解全名有助于我们对这个算法的理解。     百度翻译:satisfiability’---“可满足性,适定性”。

    合取范式可满足性问题(简称SAT问题)是一个NP完全问题

              由于SAT问题目前是NP问题,所以自然有最大化满足性问题———MAX-SAT。

              然后也有最基本的问题,生存or死亡,嫁给我or吃屎———2-SAT。

    如果能解决k-sat问题,那么他一定会火,毕竟没有很好的算法去解决,目前我们研究得更多是2-sat。

    之前做的两个2-sat题:nmphy的2-sat。第三个是今天做的,整理一下。

    -----------------------------------------------------我是分界线----------------------------------------------------------------

    浅谈2-sat:(假设读者已经知道了2sat的原理,只是有时会乱,不知道把谁作为点,谁作为边,不知道怎么建图是好)

    一般会有两个或者多个限制,要选择其中一个作为不相容限制。

    然后其他的条件作为有向图,然后判环:

    【关键】:整个算法转化成图的关键就是找好对象,判断出哪个作为不相容限制

    如果不相容限制的两个子都不能满足,那么结果为false。

            【不相容限制】:n个被选择,每个是‘真’or‘假’,代表二者不能同时存在。

            【选择限制】:m个要求,一般牵涉到两个不相容限制。

            判断是哪种限制:不相容限制再每个集合都存在,而选择限制不是。

    【例一】

    (HDU1814):题目大意:一国有n个党派,每个党派在议会中都有2个代表,现要组建和平委员会,要从每个党派在议会的代表中选出1人,一共n人组成和平委员会。已知有一些代表之间存在仇恨,也就是说他们不能同时被选为和平委员会的成员,现要你判断满足要求的和平委员会能否创立?如果能,请任意给出一种方案。

                 【不相容限制】 是每个党派的代表,设为1,2。去了一个,另一个就不能去,每个党派(集合)都存在这样的关系。

                【选择限制】     是存在仇恨的代表,并不是所有党派或者代表都存在这样的关系。

     附上代码和注释

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <vector>
    using namespace std;
    #define R 1//red 为ok 
    #define B 2//black 访问过但不ok 
    #define W 0//white 待染色 
    const int maxn = 16005;
    vector<int>G[maxn];
    int cnt,col[maxn],ans[maxn],n,m;
    bool dfs(int u)
    {
        if (col[u] == B) return false;
        if (col[u] == R) return true;
        col[u] = R;col[u^1] = B;ans[cnt++]=u;//记录染了哪些,以便失败后把颜色改回来 
        for(int i=0;i<G[u].size();i++)
            if (!dfs(G[u][i])) return false;
        return true;
    }
    bool _solve()
    {
        int i, j;
        memset(col,0,sizeof(col));
        for (i=0; i<n; i++){
            if (col[i]) continue;
            cnt=0;
            if (!dfs(i)){
                for (j=0;j<cnt;j++){
                    col[ans[j]]=W;//漂白 
                    col[ans[j]^1]=W;//漂白 
                }
                if (!dfs(i^1))  return false;//2-sat失败 
            }
        }
        return true;
    }
    int main()
    { 
        int i,a,b;
        while (~scanf("%d %d",&n, &m)){
            n<<=1;   
            for(i=0;i<=n;i++) G[i].clear();
            while (m--){
                scanf("%d %d",&a, &b);
                a--;b--;
                G[a].push_back(b^1);
                G[b].push_back(a^1);
            }
            if (_solve()){
                for (i=0; i<n; i++)
                    if(col[i] == R)
                        printf("%d
    ",i+1);
            }
            else printf("NIE
    ");
        }
        return 0;
    }
    View Code

    【例二】

    (HDU1824):集训是辛苦的,道路是坎坷的,休息还是必须的。经过一段时间的训练,lcy决定让大家回家放松一下,但是训练还是得照常进行,lcy想出了如下回家规定,每一个队(三人一队)或者队长留下或者其余两名队员同时留下;每一对队员,如果队员A留下,则队员B必须回家休息下,或者B留下,A回家。由于今年集训队人数突破往年同期最高记录,管理难度相当大,lcy也不知道自己的决定是否可行,所以这个难题就交给你了,呵呵,好处嘛~,免费**漂流一日。

               【不相容限制】 对于每个人,留或者去,二选一;

               【选择限制】     对于一对人,二留一;对于一队人,队员或队长不能同时离开。

                ®:例二有三个限制,如果没有选择好哪个是不相容限制,很可能给作图造成困难,最后爆炸。

    (每次先选小的一个。保证了字典序最小)

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <vector>
    using namespace std;
    #define R 1
    #define B 2
    #define W 0
    const int maxn = 80000;
    vector<int>G[maxn];
    int cnt,col[maxn],ans[maxn],n,m;
    bool _dfs(int u)
    {
        if (col[u] == B) return false;
        if (col[u] == R) return true;
        col[u] = R;col[u^1] = B;ans[cnt++]=u;
        for(int i=0;i<G[u].size();i++)
            if (!_dfs(G[u][i])) return false;
        return true;
    }
    bool _solve()
    {
        int i, j;
        memset(col,0,sizeof(col));
        for (i=0; i<n*6; i++){
            if (col[i]) continue;
            cnt=0;
            if (!_dfs(i)){
                for (j=0;j<cnt;j++){
                    col[ans[j]]=W;
                    col[ans[j]^1]=W;
                }
                if (!_dfs(i^1))  return false;
            }
        }
        return true;
    }
    int main()
    { 
        int i,a,b,c;
        while (~scanf("%d %d",&n, &m)){   
            for(i=0;i<n*6;i++) G[i].clear();
            for(i=1;i<=n;i++){
                scanf("%d%d%d",&a,&b,&c);
                a*=2;b*=2;c*=2;
                G[a^1].push_back(b);
                G[a^1].push_back(c);
                G[b^1].push_back(a);
                G[c^1].push_back(a);
            }
            for(i=1;i<=m;i++){
                scanf("%d%d",&a,&b);
                a*=2;b*=2;
                G[a].push_back(b^1);
                G[b].push_back(a^1);
            }
            if (_solve()) printf("yes
    "); 
            else printf("no
    ");
        }
        return 0;
    }
    View Code

     【例三】

    (hiho1467):有一场音乐会,分为上午、下午两场进行,主办方指定了n首歌让乐队进行演唱。每首歌只会被演唱一次,要么在上午要么在下午。参加音乐会的嘉宾们对于歌曲的演唱时间有一些要求。具体来说,每位嘉宾会指定两首歌曲的演唱时间(上午或者下午)。如果最后实际的演出安排中,两首歌都没有达到嘉宾的要求,那么嘉宾就会对音乐节不滿意。如嘉宾A的要求是上午《我的滑板鞋》和下午《忐忑》,而最后的演出中上午没有《我的滑板鞋》只有《忐忑》,下午没有《忐忑》只有《我的滑板鞋》,那么嘉宾A是不满意的。

    音乐节主办方自然希望使所有嘉宾满意,但主办方后来发现有可能不存在一种歌曲的安排方案满足所有嘉宾,所以他们希望你判断一下这种情况是否会发生。

                  【不相容限制】 对于每首歌,上午或者下午,二选一。(没有不相容对应的点。拆点,假设上午是i,下午是i+n

                  【选择限制】 对于每个观众,至少要完成一个要求。

    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #include<vector>
    using namespace std;
    vector<int>G[420];
    int n,m,col[420],ans[420],cnt;
    const int M=1;
    const int H=2;
    const int W=0;
    int read()
    {
        char c=getchar();
        int s,a=0;
        while(c!='h'&&c!='m') c=getchar();
        if(c=='h') a=n;
        scanf("%d",&s);
        return s+a;
    }
    int controt(int u)
    {
        if(u>n) return u-n;
        return u+n;
    }
    bool bfs(int u)
    {
        if(col[u]==M) return true;
        if(col[u]==H) return false;
        col[u]=M;col[controt(u)]=H;ans[++cnt]=u;
        for(int i=0;i<G[u].size();i++)
            if(!bfs(G[u][i])) return false;
        return true;
    }
    bool find()
    {
        memset(col,0,sizeof(col));
        for(int i=1;i<=n+n;i++){
            if(col[i]) continue;
            cnt=0;
            if(!bfs(i)){
                for(int j=1;j<=cnt;j++) {
                    col[ans[j]]=W;
                    col[controt(ans[j])]=W;
                }
                cnt=0; 
                if(!bfs(controt(i))) return false;
            }
        }
        return true;
    }
    int main()
    {
        int i,T,u,v;
        scanf("%d",&T);
        while(T--){
            scanf("%d%d",&n,&m);
            for(i=1;i<=n+n;i++) G[i].clear();
            for(i=1;i<=m;i++){
                u=read();
                v=read();
                G[u].push_back(controt(v));
                G[v].push_back(controt(u));
            }
            if(find()) printf("GOOD
    ");
            else printf("BAD
    ");
        }
        return 0;
    }
    View Code

      

    【例四】

    (hdu1815):有n个牛棚, 还有两个中转站S1和S2, S1和S2用一条路连接起来。 为了使得任意牛棚两个都可以有道路联通,现在要让每个牛棚都连接一条路到S1或者S2。有a对牛棚互相有仇恨,所以不能让他们的路连接到同一个中转站。还有b对牛棚互相喜欢,所以他们的路必须连到同一个中专站。道路的长度是两点的曼哈顿距离。问最小的任意两牛棚间的距离中的最大值是多少?

                【二分】假定ans=x

                【不相容限制】 a,b选择特定的的S,若不满足dis<=x,则相排斥。

    #include<cstdio>
    #include<cstdlib>
    #include<iostream>
    #include<cstring>
    #include<algorithm>
    #include<vector>
    using namespace std;
    const int maxn=1010;
    const int B=1;
    const int R=2;
    const int W=0;
    vector<int>G[maxn];
    vector<int>G2[maxn];
    int dis[3][maxn],Dis;//Dis是S点,T点 
    int x,y,x1,y1,x2,y2,a,b,n,ans;
    int col[maxn],q[maxn],num;
    void init()
    {
        for(int i=1;i<=2*n;i++) G[i].clear();
        for(int i=1;i<=2*n;i++) G2[i].clear();
        ans=-1;
    }
    void scan()
    {
            int i;
            scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
            Dis=abs(x1-x2)+abs(y1-y2);
            for(i=1;i<=n;i++){
                    scanf("%d%d",&x,&y);
                    dis[1][i]=abs(x-x1)+abs(y-y1);
                    dis[2][i]=abs(x-x2)+abs(y-y2);
            }
            for(i=1;i<=a;i++){
                    scanf("%d%d",&x,&y);
                    G[x].push_back(y+n);
                    G[x+n].push_back(y);
                    G[y].push_back(x+n);
                    G[y+n].push_back(x);
            }
            for(i=1;i<=b;i++){
                    scanf("%d%d",&x,&y);
                    G[x].push_back(y);
                    G[y].push_back(x);
                    G[x+n].push_back(y+n);
                    G[y+n].push_back(x+n);
            }
            
    }
    bool dfs(int u)
    {
        if(col[u]==R) return false;
        if(col[u]==B) return true;
        col[u]=B;
        col[u>n?u-n:u+n]=R;
        q[++num]=u;
        for(int i=0;i<G[u].size();i++)  if(!dfs(G[u][i])) return false;
        for(int i=0;i<G2[u].size();i++) if(!dfs(G2[u][i])) return false;
        return true;
    }
    bool check(int x)
    {
        int i,j;
        for(i=1;i<=n;i++) if(dis[1][i]>x&&dis[2][i]>x) return false;
        for(i=1;i<=2*n;i++) G2[i].clear();
        for(i=1;i<=2*n;i++) col[i]=0; 
        for(i=1;i<=n;i++)
         for(j=i+1;j<=n;j++){
              int d1=dis[1][i]+dis[1][j];
             int d2=dis[1][i]+dis[2][j]+Dis;
             int d3=dis[2][i]+dis[2][j];
             int d4=dis[2][i]+dis[1][j]+Dis;
             if(d1>x&&d2>x&&d3>x&&d4>x) return false;
             if(d1>x){
                    G2[i].push_back(j+n);
                    G2[j].push_back(i+n);
             }
             if(d2>x){
                    G2[i].push_back(j);
                    G2[j+n].push_back(i+n);
             }
             if(d3>x){
                    G2[i+n].push_back(j);
                    G2[j+n].push_back(i);
             }
             if(d4>x){
                    G2[i+n].push_back(j+n);
                    G2[j].push_back(i);
             }
        }
         for(i=1;i<=2*n;i++){
              if(col[i]) continue;
              num=0;
              if(!dfs(i)){
                  for(j=1;j<=num;j++) {
                      col[q[j]>n?q[j]-n:q[j]+n]=W;
                      col[q[j]]=W;
                  }
                  if(!dfs(i>n?i-n:i+n)) return false;    
              }      
         }
         return true;
    }
    int main()
    {
         while(~scanf("%d%d%d",&n,&a,&b)){
                init();
                int L=0,R=8000000;
                scan();
                while(L<=R){
                    int mid=(L+R)>>1;
                    if(check(mid)){ ans=mid;R=mid-1;}
                    else  L=mid+1;
                }
                printf("%d
    ",ans);
         }
         return 0;
    }
    View Code

         

            

    本文仅阐述如何寻找不相容限制,然后去建图。

    所以代码的优化在这里没有提及,用【缩点】【拓扑】的优化将在下文补充。

    如果你对2-sat有什么新的认识,或者疑问,请留言额。待续。。。

  • 相关阅读:
    C#编写的windows程序随系统启动
    CentOS 6.0修改ssh远程连接端口
    C# 控件缩写大全+命名规范+示例
    记录点滴
    C++封装,继承,多态,友元
    策略模式
    OpenGL入门
    C++四种类型转换
    观察者模式Observer Pattern
    双向链表std::list和单向链表std::forward_list
  • 原文地址:https://www.cnblogs.com/hua-dong/p/7844924.html
Copyright © 2020-2023  润新知