zoukankan      html  css  js  c++  java
  • 2-SAT 小结

      PS:今天(2014.10.27)准备PPT,明天在组合数学课上与大家一起分享一下2-SAT。我以为是一件简单的事情。但是,当我看了自己这篇博客以后,发现居然还是不懂。很多资料不全,也没仔细讲。整理了一下,又修改了。尽量让自己下次再看到这博客的时候,一遍下来就能懂。虽然引用了别人的博客,我尽量讲得不需要进入别人的博客就能看懂。

      一些这方面的内容写得很好的博客。(排列是随机的。可以略过了)

      http://www.cnblogs.com/kuangbin/archive/2012/10/05/2712429.html  解释很清晰,但是代码风格不是我想要的。

      http://blog.csdn.net/hqd_acm/article/details/5881655  内容很详细,方法也很好,不过没有代码是个致命缺憾。

    定义:

      如果存在一个真值分配,使得布尔表达式的取值为真,则这个布尔表达式称为可适定的,简称SAT

      例如(x1+x2)(┐x1+┐x2)是一个布尔表达式,如果p(x1)=“真”,p(x1)=“假”,则表达式的值为真,则这个表达式是适定的。上面的“+”是逻辑或。不是所有的布尔表达式都是可适定的,必须存在使得表达式为真的情况。

    2-SAT:设X={x1,x2,…,xn,┐x1,┐x2,…,┐xn},布尔表达式中只有两种形式,一种是单个布尔变量a属于X,另一种是两个变量a,b的或(a+b),a,b都属于X。

       例如:(a+b)·(c+d)·e ,这是一个合取范式,每一个布尔表达式,如(a+b)最多只有两个元素。k-SAT,当k>2时,问题为完全的。

       2-SAT问题就是求使表达式为真的解。

      学习2-SAT必须先掌握强连通和拓扑排序。

    2-SAT有三种题型,不同题型的解题方法不同。

      第一种,暴力求最小字典序解。复杂度O(n*m)。

      第二种,强连通判断有没有解。O(n+m)。

      第三种,强连通+缩点+拓扑排序,求任意解。O(n+m)(第三种会了,第二种也就会了,严格说来,第二种应该和第三种合并成第二种)

      虽然题型不同,但是建图的方式还是相同的。边u->v的含义是选择了u ,就必须选择v。 

      

      第一种类型,暴力求最小字典序解。

      典型题目 hdu1814 Peaceful Commission

      题目大意:

       根据宪法,Byteland民主共和国的公众和平委员会应该在国会中通过立法程序来创立。

        不幸的是,由于某些党派代表之间的不和睦而使得这件事存在障碍。
        此委员会必须满足下列条件:
          1、每个党派都在委员会中恰有1个代表,
          2、如果2个代表彼此厌恶,则他们不能都属于委员会。
          3、每个党在议会中有2个代表。代表从1编号到2n。 编号为2i-1和2i的代表属于第I个党派。
        计算决定建立和平委员会是否可能,若行,则列出委员会的成员表。求出字典序最小的解
    题目分析:
     此题麻烦之处在于要求出字典序最小的解。所以才用暴力枚举来做,也就是一个一个试过去。找到解就是最小的字典序解。

      我们给结点染色,假设白色是未染色,红色是要选取的结点,蓝色是抛弃的结点。
      首先从第一个点开始染色,染成红色,同时将同组另一节点染成蓝色,然后将这个点的所有后继结点也染成红色,同时开一个数组记录都染了哪些结点。如果后来发现某结点的后继是蓝色,说明本次染色失败,因为碰到了矛盾(假如一个结点被选取,那么所有后继肯定也得全部选取)。那么因为刚才染色的时候记录了染色的结点,靠这个数组将结点全部还原回白色。然后从第二个结点开始探索,直到全部染色完毕,最后的红色结点就是答案。

      这种方法的时间复杂度为O(m*n)。m为边数,n为顶点数。

    此题最好的题解(我认为)是http://blog.163.com/shengrui_step/blog/static/20870918720141201262750/  染色,很容易懂
    下面是我的代码:vis[]标记选择,-1为没有标记,1是标记为选择,0为放弃。
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int N=20010,M=100010;
    int vis[N],head[N],ch[N];
    int tot,cnt;
    struct node
    {
        int to,next;
    }edge[M];
    void addedge(int i,int j)
    {
        edge[tot].to=j;edge[tot].next=head[i];head[i]=tot++;
    }
    int dfs(int u)
    {
        if(vis[u]==1) return 1;
        if(vis[u]==0) return 0;
        vis[u]=1;
        vis[u^1]=0;
        ch[cnt++]=u;
        for(int k=head[u];k!=-1;k=edge[k].next)
            if(!dfs(edge[k].to)) return 0;
        return 1;
    }
    int sat2(int n)
    {
        for(int i=0;i<2*n;i++)
        {
            if(vis[i]==-1)
            {
                cnt=0;
                if(!dfs(i))
                {
                    for(int j=0;j<cnt;j++)
                        vis[ch[j]]=vis[ch[j]^1]=-1;
                    if(!dfs(i^1)) return 0;
                }
            }
        }
        return 1;
    }
    void init()
    {
        tot=0;
        memset(head,-1,sizeof(head));
        memset(vis,-1,sizeof(vis));
    }
    int main()
    {
        //freopen("test.txt","r",stdin);
        int n,m,i,j,k;
        while(scanf("%d%d",&n,&m)!=EOF)
        {
            init();
            while(m--)
            {
                scanf("%d%d",&i,&j);
                i--;j--;
                addedge(i,j^1);
                addedge(j,i^1);
            }
            if(sat2(n))
            {
                for(i=0;i<2*n;i++)
                    if(vis[i]==1)printf("%d
    ",i+1);
            }
            else printf("NIE
    ");
        }
        return 0;
    }
    View Code
    
    

    第二种类型,强连通求可行解。
    典型题目 hdu3622 Bomb Game
    参照http://www.cnblogs.com/kuangbin/archive/2012/10/05/2712424.html 不过,要说一说,强连通的三个模版中,二次搜索的那个很弱的。
    题目大意:
      给n对炸弹可以放置的位置(每个位置为一个二维平面上的点),每次放置炸弹是时只能选择这一对中的其中一个点,每个炸弹爆炸的范围半径都一样,控制爆炸的半径使得所有爆炸范围都不相交(可以相切),求解这个最大半径。
    分析:
      求强连通的目的是什么?属于一个强连通的点是必须一起选择的。如果一对关系点(只能且必须选其中一个)属于同一个强连通,就产生了矛盾,就无解。这是这种题型的解题思想。
      这题多了二分查找,显得麻烦。但是二分技术在最大流和二分匹配中都有的。懂了就好了。
    时间复杂度是O(m),主要是求强连通分量。
    下面的我的代码
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    using namespace std;
    
    const int N =210, M=100010;
    const double eps=10e-6;
    struct node
    {
        int to, next;
    }edge[M];
    int head[N], low[N], dfn[N], sta[N], belg[N], num[N];
    bool vis[N];
    int scc,index,top, tot;
    void tarbfs(int u)
    {
        int i,j,k,v;
        low[u]=dfn[u]=++index;
        sta[top++]=u;
        vis[u]=1;
        for(i=head[u];i!=-1;i=edge[i].next)
        {
            v=edge[i].to;
            if(!dfn[v])
            {
                tarbfs(v);
                if(low[u]>low[v]) low[u]=low[v];
            }
            else if(vis[v]&&low[u]>dfn[v]) low[u]=dfn[v];
        }
        if(dfn[u]==low[u])
        {
            scc++;
            do
            {
                v=sta[--top];
                vis[v]=0;
                belg[v]=scc;
                num[scc]++;
            }
            while(v!=u) ;
        }
    }
    bool Tarjan(int n)
    {
        memset(vis,0,sizeof(vis));
        memset(dfn,0,sizeof(dfn));
        memset(num,0,sizeof(num));
        memset(low,0,sizeof(low));
        index=scc=top=0;
        for(int i=0;i<n;i++)
            if(!dfn[i]) tarbfs(i);
        for(int i=0;i<n;i+=2)
            if(belg[i]==belg[i+1]) return 0;
        return 1;
    }
    void init()
    {
        tot=0;
        memset(head,-1,sizeof(head));
    }
    void addedge(int i,int j)
    {
        edge[tot].to=j; edge[tot].next=head[i];head[i]=tot++;
    }
    struct point
    {
        int x,y;
    }s[N];
    double Distance(point a, point b)
    {
        return sqrt((double)(a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
    }
    int main()
    {
        //freopen("test.txt","r",stdin);
        int n,i,j,k;
        double left,right,mid;
        while(scanf("%d",&n)!=EOF)
        {
            for(i=0;i<n;i++)
                scanf("%d%d%d%d",&s[2*i].x,&s[2*i].y,&s[2*i+1].x,&s[2*i+1].y);
            left=0; right=40000.0;
            n*=2;
            while(right-left>=eps)
            {
                mid=(left+right)/2;
                init();
                for(i=0;i<n-2;i++)
                {
                    if(i%2==0) k=i+2;
                    else k=i+1;
                    for(j=k;j<n;j++)
                        if(Distance(s[i],s[j])<2*mid)
                        {
                            addedge(i,j^1);
                            addedge(j,i^1);
                        }
                }
                if(Tarjan(n)) left=mid;
                else right=mid;
            }
            printf("%0.2f
    ",right);
        }
        return 0;
    }
    View Code

    第三种题型,强连通+拓扑排序求任意解。

    典型题目:poj3622
    讲得特别好的博客: http://blog.csdn.net/qq172108805/article/details/7603351
    题意:有对情侣结婚,请来n-1对夫妇,算上他们自己共n对,编号为0~~n-1,他们自己编号为0.所有人坐在桌子两旁,新娘不想看到对面的人有夫妻关系或偷奸关系,若有解,输出一组解,无解输出bad luck 。
    题目分析:
      题目的大致意思可以理解为把HDU1814求字典序最小的解改为求任意解。
    这一类的题目的做法是:
      第一步,建图。规则是边<u,v>表示选u就必须选v。
      第二步,求强连通分量。这一步我觉得就是用模版了。
      第三步,对每一对点进行判断,如果它们属于同一强连通,就无解。有解才进行下面的步骤。比如此题,通奸的人不能做同一边。
      第四步,对缩点后的图进行反向建图。
      第五步,对建的图进行拓扑排序染色。选择一个没有被染色的点a,将其染成红色,把所有与a点矛盾的点b和b的子孙染成黑色。不断重复操作,知道没有点可以染色而止。
      最后,红色的点就是一个解。
    时间复杂度是O(m),主要是求强连通分量。
    要注意的是:强连通缩点以后,建立的是反向图。
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int N =210, M=100010;
    struct node
    {
        int to, next;;
    }edge[M],e[M];
    int stk[N],stk2[N],head[N],low[N],belg[N];
    int id[N],vis[N],h[N], t, que[N],dui[N];
    int cn,cm,tot,scc,lay;
    int Garbowbfs(int cur,int lay)
    {
        stk[++cn]=cur; stk2[++cm]=cur;
        low[cur]=++lay;
        for(int i=head[cur];i!=-1;i=edge[i].next)
        {
            int v=edge[i].to;
            if(!low[v]) Garbowbfs(v,lay);
            else if(!belg[v])
                while(low[stk2[cm]]>low[v]) cm--;
        }
        if(stk2[cm]==cur)
        {
            cm--;
            scc++;
            do
                belg[stk[cn]]=scc;
            while(stk[cn--]!=cur) ;
        }
        return 0;
    }
    
    bool Garbow(int n)
    {
        scc=lay=0;
        memset(belg,0,sizeof(belg));
        memset(low,0,sizeof(low));
        for(int i=0;i<n;i++)//顶点从0开始(若是1需要修改)
            if(!low[i]) Garbowbfs(i,lay);
        for(int i=0;i<n;i+=2)
        {
            if(belg[i]==belg[i+1]) return 0;
            dui[belg[i]]=belg[i+1];
            dui[belg[i+1]]=belg[i];
        }
        return 1;
    }
    void addedge(int i,int j)
    {
        edge[tot].to=j; edge[tot].next=head[i];head[i]=tot++;
    }
    void add_e(int i,int j)
    {
        e[t].to=j; e[t].next=h[i];h[i]=t++;
    }
    void init()
    {
        tot=t=0;
        memset(head,-1,sizeof(head));
        memset(h,-1,sizeof(h));
        memset(vis,-1,sizeof(vis));
        memset(id,0,sizeof(id));
    }
    void topo()
    {
        int i,k,j=0;
        for(i=0;i<scc;i++)
            if(id[i]==0) que[j++]=i;
        for(i=0;i<j;i++)
        {
            int u=que[i];
            if(vis[u]==-1)
            {
                vis[u]=1;
                vis[dui[u]]=0;
            }
            for(k=h[u];k!=-1;k=e[k].next)
            {
                int v=e[k].to;
                id[v]--;
                if(id[v]==0)
                    que[j++]=v;
            }
        }
    }
    int main()
    {
        //freopen("test.txt","r",stdin);
        int n,m,i,j,k;
        char ch1,ch2;
        while(scanf("%d%d",&n,&m)!=EOF)
        {
            if(!n) break;
            init();
            while(m--)
            {
                scanf("%d%c %d%c",&i,&ch1,&j,&ch2);
                if(ch1=='w') i=2*i;
                else i=2*i+1;
                if(ch2=='w') j=2*j;
                else j=2*j+1;
                addedge(i,j^1);
                addedge(j,i^1);
            }
            addedge(0,1);
            n*=2;
            if(Garbow(n)==0) {printf("bad luck
    "); continue;}
            for(i=0;i<n;i++)//反向图
            {
                for(k=head[i];k!=-1;k=edge[k].next)
                {
                    int u=belg[i], v=belg[edge[k].to];
                    if(u!=v)
                    {
                        add_e(v,u);
                        id[u]++;
                    }
                }
            }
            topo();
            for(i=2;i<n;i+=2)
            {
                k=belg[i];
                if(vis[k]==1)printf("%dh ",i/2);
                else printf("%dw ",i/2);
            }
            printf("
    ");
        }
        return 0;
    }
    View Code
    
    
    
     
    
    
    
     
  • 相关阅读:
    Angular4 后台管理系统搭建(5)
    Angular4 后台管理系统搭建(4)
    Angular4 后台管理系统搭建(3)
    Angular4 后台管理系统搭建(2)
    Angular4 后台管理系统搭建(1)
    训练x_vector时kaldi的模型选择机制
    investment
    拉单杠
    programming blogs
    pronunciation from longman 718
  • 原文地址:https://www.cnblogs.com/Potato-lover/p/3965512.html
Copyright © 2011-2022 走看看