Live2d Test Env

【整理】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\n",i+1);
        }
        else printf("NIE\n");
    }
    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\n"); 
        else printf("no\n");
    }
    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\n");
        else printf("BAD\n");
    }
    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\n",ans);
     }
     return 0;
}
View Code

 

 

     

 

 

        

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

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

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

posted @ 2017-11-16 17:11  nimphy  阅读(483)  评论(1编辑  收藏  举报