2-SAT

一坨题

bzoj1997
bzoj2199
bzoj1823
bzoj4078

poj2723√
题意:
有2*n把钥匙分成n组,每组两个钥匙如果选择了一把另一把就会消失。有m个门,每个门上有两把锁,只要打开一把锁就能打开门,要打开第i+1个门必须先打开第i个门。
求最多能打开多少门。

分析:
每把钥匙有两个状态(用或不用),把这作为2-sat的两个选项
然后是加条件:
a、b绑在一起,则选a就不选b,选b就不选a,建边 a>!b,b>!a
c、d在同一个门上,则不开c就开d,不开d就开c,建边 !c>d,!d>c
然后二分答案都可以了

poj2749
题意:
好像是平面上有两个点 s1 s2 ,这两个点是连在一起的,然后给了n个点,然后这些点要么连 s1 要么连 s2 ,要求任意两点的最大距离最小,然后有些点不能连在同一边,也就是不能同时连 s1 或者 s2 ,有些点必须连在同一边,就是同时连 s1 或者同时连 s2 。这题要求的是曼哈顿距离。两个点a和b的距离有4种, a>s1>b ; a>s2>b ; a>s1>s2>b ; a>s2>s1>b
求最长的点之间的距离最短是多少。

poj3207
题意:
一个圆周上有n个点标号依次从0到n-1,然后有m条线段每个线段以其中两个点为端点,线可以在圆正面或者背面。
问是否存在一种方式使得任意两线段都不相交,端点相交不算。

poj3648√
题意:
一堆夫妇去参加一对新人的婚礼。人们坐在一个很长很长的桌子的两侧(面对面)。新郎新娘在桌子头面对面座。
新娘不希望看见她对面的一排有一对夫妇坐着(夫妇需要分开两排座)。
同时,一些人之间有暧昧关系(同性异性均有可能),新娘也不希望有暧昧关系的人同时坐在她对面的一排。
问你可否满足新娘的要求,可以的话,输出一种方案。
分析:
根据题意建图。都是 ax or ay (一个要输出方案的模板)

poj3678
题意:
构造一个长度为N的01数列,使得第i位和第j位通过特定的逻辑运算结果为k。

poj3683
题意:
有n个婚礼,每个婚礼有起始时间si,结束时间ti,还有一个主持时间ti,ti必须安排在婚礼的开始或者结束,主持由祭祀来做,但是只有一个祭祀,所以各个婚礼的主持时间不能重复,问你有没有可能正常的安排主持时间,不能输出no,能的话要输出具体的答案:即每个婚礼的主持时间段是什么样的。

poj2296
题意:
坐标轴上有N个点,要在每个点上贴一个正方形,这个正方形的横竖边分别和x,y轴平行,并且要使得点要么在正方形的上面那条边的中点,或者在下面那条边的中点,并且任意两个点的正方形都不重叠(可以重边)。问正方形最大边长可以多少?

poj3905
题意:
2-SAT_第1张图片

参考资料:
http://www.cppblog.com/MatoNo1/archive/2011/07/13/150766.html
http://blog.csdn.net/lethic/article/details/7803841
http://blog.csdn.net/d891320478/article/details/8287435

问题描述

现有一个由N个布尔值组成的序列A,给出一些限制关系,比如 ax and ay=0ax or ay or az=1 等,要确定 a0..an 的值,使得其满足所有限制关系。这个称为SAT问题.
特别的,若每种限制关系中最多只对两个元素进行限制,则称为2-SAT问题。

问题分析

由于在2-SAT问题中,最多只对两个元素进行限制,所以可能的限制关系共有11种:
ax
!ax
ax and ay
ax and !ay
ax or ay
ax or !ay
ax xor ay
ax xor !ay
!(ax and ay)
!(ax or ay)
!(ax xor ay
发现: !(ax or ay)=!ax and !ay
ax and ayax and !ay!ax and !ay 都是 ax ay 之间的关系。所以限制关系只有9种

在实际问题中,2-SAT问题在大多数时候表现成以下形式:有N对物品,每对物品中必须选取一个,也只能选取一个,并且它们之间存在某些限制关系(如某两个物品不能都选,某两个物品不能都不选,某两个物品必须且只能选一个,某个物品必选)等,这时,可以将每对物品当成一个布尔值(选取第一个物品相当于0,选取第二个相当于1),如果所有的限制关系最多只对两个物品进行限制,则它们都可以转化成9种基本限制关系,从而转化为2-SAT模型。

建模

其实2-SAT问题的建模是和实际问题非常相似的。
建立一个2N阶的有向图,其中的点分为N对,每对点表示布尔序列A的一个元素的0、1取值(以下将代表A[i]的0取值的点称为i,代表A[i]的1取值的点称为i’)。显然每对点必须且只能选取一个。然后,图中的边具有特定含义。若图中存在边 <i,j> ,则表示若选了i必须选j
可以发现,上面的9种限制关系中,后7种二元限制关系都可以用连边实现,比如:
!(ax and ay) 需要连两条边 <x,y> <y,x>
ax or ay 需要连两条边 <x,y> <y,x>
而前两种一元关系:
ax (即x必选),可以通过连边 <x,x> 来实现,
!ax (即x不能选),可以通过连边 <x,x> 来实现。

判断是否可行

因为连边关系->具有传递性,故如果存在一个强连通分量,则选取其中任意一个结点,其余的结点也同样会被选到,故可以先缩点,如果x和~x在同一个强连通分量里,则无解。
否则,得到新的有向无环图。
在新图中按照拓扑反序开始选择,若当前结点没有选择,则选择当前结点并删除对称结点以及对称结点的前驱。
因为采用了拓扑反序,不存在未处理的后继

//模板poj3648
#include
#include
#include

using namespace std;
const int N=100100;
int dfn[N],low[N],scc[N];
int q[N],v[N],top,tm,cnt;
int h[N],ans[N],col[N];
int deg[N],c[N];
int n,m,tot,x,y;
struct edge{int y,next;}g[N];
struct bian{int x,y;}b[N];
//-------------------原图增加一条边
void adp(int x,int y){
    g[++tot].y=y;
    b[tot].x=x;
    b[tot].y=y;
    g[tot].next=h[x];
    h[x]=tot;
}
//-------------------缩点图增加一条边
void ads(int x,int y){
    g[++tot].y=y;
    g[tot].next=h[x];
    h[x]=tot;
}
//-------------------Tarjan算法求强连通分量
void tarjan(int x){
    dfn[x]=low[x]=++tm;
    q[++top]=x;v[x]=1;
    for (int i=h[x];i;i=g[i].next)
    if (!dfn[g[i].y]){
        tarjan(g[i].y);
        low[x]=min(low[x],low[g[i].y]);
    } else 
    if (v[g[i].y]) low[x]=min(low[x],dfn[g[i].y]);
    if (low[x]==dfn[x]){
        cnt++;
        while (q[top]!=x){
            scc[q[top]]=cnt;
            v[q[top--]]=0;
        }
        scc[q[top]]=cnt;
        v[q[top--]]=0;
    }
}
//---------------------拓扑排序求解
int check(){
    for (int i=0;i<2*n;i++)
    if (!dfn[i]) tarjan(i);
    for (int i=0;iif (scc[i]==scc[i+n])
        return 0; else
        {
            c[scc[i]]=scc[i+n];
            c[scc[i+n]]=scc[i];//在缩点的图中标记互斥的缩点。(原来互斥,现在也互斥)
        }
    memset(h,0,sizeof(h));
    memset(col,0,sizeof(col));
    int tnt=tot;tot=0;
    for (int i=1;i<=tnt;i++)
    if (scc[b[i].x]!=scc[b[i].y]){
        ads(scc[b[i].y],scc[b[i].x]);//反向,是因为u--->v,如果选择了u,必须选择v,则应该反向,求入度为0的缩点
        deg[scc[b[i].x]]++;
    }
    //-----------------开始拓扑排序
    int l=0,r=0;
    for (int i=1;i<=cnt;i++)
        if (!deg[i])    
            q[++r]=i;
    while (lint x=q[++l];
        if (!col[x]){col[x]=1;col[c[x]]=-1;}//对于未着色的点x,将x染成红色1,同时将与x互斥的点c[x]染成蓝色-1for (int i=h[x];i;i=g[i].next){
            if (--deg[g[i].y]==0)
                q[++r]=g[i].y;
        }
    }
    for (int i=0;iif (col[scc[i]]==1)
            ans[i]=1;
    return 1;
}

int main(){
    while (scanf("%d%d",&n,&m) && n && m){
        memset(h,0,sizeof(h));
        memset(dfn,0,sizeof(dfn));
        memset(low,0,sizeof(low));
        memset(v,0,sizeof(v));
        memset(ans,0,sizeof(ans));
        memset(deg,0,sizeof(deg));
        tot=cnt=top=tm=0;
        for (int i=1;i<=m;i++){
            char s1,s2;
            scanf("%d%c %d%c",&x,&s1,&y,&s2);
            if (s1=='h' && s2=='h')
                adp(y+n,x),adp(x+n,y);
            if (s1=='h' && s2=='w')
                adp(x+n,y+n),adp(y,x);
            if (s1=='w' && s2=='h')
                adp(x,y),adp(y+n,x+n);
            if (s1=='w' && s2=='w')
                adp(x,y+n),adp(y,x+n);
        }
        adp(0,n);
        if (check()){
            for (int i=1;iif (ans[i])
                printf("%dh ",i); else
                printf("%dw ",i);
        } else printf("bad luck");
        printf("\n");
    }
}

你可能感兴趣的:(各种算法,2-SAT)