2-sat

2-sat问题

 

构图传送门

方案可行性O(M)算法:

建好图之后 利用tarjan强连通算法,最后判断每个点的1和0是否在一个强连通块里。

#include<iostream>
#include<cstdio>
#include<cstring>

using namespace std;

struct{
    int to,next;
}edge[30000100];    //边结点数组

int head[10005],stack[10005],dfn[10005],low[10005],belong[10005];
//  head[]头结点数组,stack[]为栈,dfn[]为深搜次序数组,belong[]为每个结点所对应的强连通分量标号数组
//      low[u]为u结点或者u的子树结点所能追溯到的最早栈中结点的次序号
int instack[10005],cnt,scnt,top,n,tot;
// instack[]为是否在栈中的标记数组

void Add(int x,int y){    //构建邻接表
    edge[tot].to=y;
    edge[tot].next=head[x];
    head[x]=tot++;
}

void Tarjan(int v)       //Tarjan算法求有向图的强连通分量
{
    int min,i,t,j;
    dfn[v]=low[v]=++cnt;    //cnt为次序计数器
    instack[v]=1;    //标记在栈中
    stack[top++]=v;      //入栈
    for(i=head[v];i!=-1;i=edge[i].next){   //枚举v的每一条边
        j=edge[i].to;   //v所邻接的边
        if(!dfn[j]){   //未被访问
            Tarjan(j);    //继续向下找
            if(low[v]>low[j])low[v]=low[j];  // 更新结点v所能到达的最小次数层
        }else if(instack[j]&&dfn[j]<low[v]){   //如果j结点在栈内,
                low[v]=dfn[j];
        }
    }
    if(dfn[v]==low[v]){     //如果节点v是强连通分量的根
        scnt++;   //连通分量标号加1
        do{
            t=stack[--top];   //退栈
            instack[t]=0;   //标记不在栈中
            belong[t]=scnt;   //出栈结点t属于cnt标号的强连通分量
        }while(t!=v);  //直到将v从栈中退出
    }
}

bool Slove(){
    int i;
    scnt=cnt=top=0;    //初始化连通分量标号,次序计数器,栈顶指针为0
    memset(dfn,0,sizeof(dfn));    //结点搜索的次序编号数组为0,同时可以当是否访问的数组使用
    for(i=0;i<2*n;i++)   //枚举每个结点,搜索连通分量
        if(!dfn[i])  //未被访问
            Tarjan(i);  //则找i结点的连通分量
    for(int i=0;i<n;i++)
        if(belong[2*i]==belong[2*i+1])//相等表示两个数据在同一个集合之中
            return false;
    return true;
}

int main(){
    int m,i,x,y,c1,c2,a,b,c,d;
    while(~scanf("%d",&n))
    {
        memset(head,-1,sizeof(head));   //邻接表的头结点数组初始化为-1
        memset(stack,0,sizeof(instack));
        memset(belong,-1,sizeof(belong));
        memset(low,0,sizeof(low));
        scanf("%d",&m);
        tot=0;
        while(m--){
            scanf("%d%d%d%d",&a,&b,&c,&d);
            Add((a<<1)+c,(b<<1)+1-d);
            Add((b<<1)+d,(a<<1)+1-c);
            /*Add(2*a+c,2*b+(1-d));
            Add(2*b+d,2*a+(1-c));*/
        }
        //求强连通分量
        if(Slove())       //只有一个强连通分量,说明此图各个结点都可达
            printf("YES\n");
        else
            printf("NO\n");
    }
    return 0;

}
View Code

 

hdu 1824

题意多个group,每个group有个队长,两个队员。一个group至少要队长留下或者两个队员同时留下。然后给边表示这两个队员不能同时留下。

(开始一直想怎么让两个队员并到一起呢?看了别人的代码,大悟2-sat的“精髓”,每个点有两个状态,每个队员当一个点来处理,so great。)

#include<iostream>
#include<cstdio>
#include<cstring>

using namespace std;

struct{
    int to,next;
}edge[30000100];    //边结点数组

int head[10005],stack[10005],dfn[10005],low[10005],belong[10005];
//  head[]头结点数组,stack[]为栈,dfn[]为深搜次序数组,belong[]为每个结点所对应的强连通分量标号数组
//      low[u]为u结点或者u的子树结点所能追溯到的最早栈中结点的次序号
int instack[10005],cnt,scnt,top,n,tot;
// instack[]为是否在栈中的标记数组

void Add(int x,int y){    //构建邻接表
    edge[tot].to=y;
    edge[tot].next=head[x];
    head[x]=tot++;
}

void Tarjan(int v)       //Tarjan算法求有向图的强连通分量
{
    int min,i,t,j;
    dfn[v]=low[v]=++cnt;    //cnt为次序计数器
    instack[v]=1;    //标记在栈中
    stack[top++]=v;      //入栈
    for(i=head[v];i!=-1;i=edge[i].next){   //枚举v的每一条边
        j=edge[i].to;   //v所邻接的边
        if(!dfn[j]){   //未被访问
            Tarjan(j);    //继续向下找
            if(low[v]>low[j])low[v]=low[j];  // 更新结点v所能到达的最小次数层
        }else if(instack[j]&&dfn[j]<low[v]){   //如果j结点在栈内,
                low[v]=dfn[j];
        }
    }
    if(dfn[v]==low[v]){     //如果节点v是强连通分量的根
        scnt++;   //连通分量标号加1
        do{
            t=stack[--top];   //退栈
            instack[t]=0;   //标记不在栈中
            belong[t]=scnt;   //出栈结点t属于cnt标号的强连通分量
        }while(t!=v);  //直到将v从栈中退出
    }
}

bool Slove(){
    int i;
    scnt=cnt=top=0;    //初始化连通分量标号,次序计数器,栈顶指针为0
    memset(dfn,0,sizeof(dfn));    //结点搜索的次序编号数组为0,同时可以当是否访问的数组使用
    for(i=0;i<2*n;i++)   //枚举每个结点,搜索连通分量
        if(!dfn[i])  //未被访问
            Tarjan(i);  //则找i结点的连通分量
    for(int i=0;i<n;i++)
        if(belong[2*i]==belong[2*i+1])//相等表示两个数据在同一个集合之中
            return false;
    return true;
}
int z[100001];

int main(){
    int m,i,x,y,c1,c2,a,b,c,d;
    while(~scanf("%d%d",&n,&m))
    {
        memset(head,-1,sizeof(head));   //邻接表的头结点数组初始化为-1
        memset(stack,0,sizeof(instack));
        memset(belong,-1,sizeof(belong));
        memset(low,0,sizeof(low));
        tot=0;
        int id=0;
        for(int i=0;i<n;i++){
            scanf("%d%d%d",&a,&b,&c);
            Add((a<<1)+1,(b<<1));
            Add((a<<1)+1,(c<<1));
            Add((b<<1)+1,(a<<1));
            Add((c<<1)+1,(a<<1));
        }
        int flag=0;
        while(m--){
            scanf("%d%d",&a,&b);
            Add((a<<1),(b<<1)+1);
            Add((b<<1),(a<<1)+1);
            /*Add(2*a+c,2*b+(1-d));
            Add(2*b+d,2*a+(1-c));*/
        }
        if(flag){printf("NO\n");continue;}
        //求强连通分量
        if(Slove())       //只有一个强连通分量,说明此图各个结点都可达
            printf("yes\n");
        else
            printf("no\n");
    }
    return 0;

}
View Code

 

hdu 3622

题意给了n对点,没对任选一个点做炸弹的圆心 每个炸弹之间不能有重叠 求炸弹最大最大半径。

 

二分+2-sat

在于建图,对于一个radio 判断每个点之间距离,小于2*radio这说明这两个是不能同时存在的。 构好图对于该2-sat的可行性判断。

  1 #include<iostream>
  2 #include<cstdio>
  3 #include<cstring>
  4 #include<cmath>
  5 #define eps 1e-6
  6 using namespace std;
  7 
  8 
  9 struct{
 10     int to,next;
 11 }edge[30000100];    //边结点数组
 12 
 13 int head[10005],stack[10005],dfn[10005],low[10005],belong[10005];
 14 //  head[]头结点数组,stack[]为栈,dfn[]为深搜次序数组,belong[]为每个结点所对应的强连通分量标号数组
 15 //      low[u]为u结点或者u的子树结点所能追溯到的最早栈中结点的次序号
 16 int instack[10005],cnt,scnt,top,n,tot;
 17 // instack[]为是否在栈中的标记数组
 18 
 19 void Add(int x,int y){    //构建邻接表
 20     edge[tot].to=y;
 21     edge[tot].next=head[x];
 22     head[x]=tot++;
 23 }
 24 
 25 void Tarjan(int v)       //Tarjan算法求有向图的强连通分量
 26 {
 27     int min,i,t,j;
 28     dfn[v]=low[v]=++cnt;    //cnt为次序计数器
 29     instack[v]=1;    //标记在栈中
 30     stack[top++]=v;      //入栈
 31     for(i=head[v];i!=-1;i=edge[i].next){   //枚举v的每一条边
 32         j=edge[i].to;   //v所邻接的边
 33         if(!dfn[j]){   //未被访问
 34             Tarjan(j);    //继续向下找
 35             if(low[v]>low[j])low[v]=low[j];  // 更新结点v所能到达的最小次数层
 36         }else if(instack[j]&&dfn[j]<low[v]){   //如果j结点在栈内,
 37                 low[v]=dfn[j];
 38         }
 39     }
 40     if(dfn[v]==low[v]){     //如果节点v是强连通分量的根
 41         scnt++;   //连通分量标号加1
 42         do{
 43             t=stack[--top];   //退栈
 44             instack[t]=0;   //标记不在栈中
 45             belong[t]=scnt;   //出栈结点t属于cnt标号的强连通分量
 46         }while(t!=v);  //直到将v从栈中退出
 47     }
 48 }
 49 
 50 bool Slove(){
 51     int i;
 52     scnt=cnt=top=0;    //初始化连通分量标号,次序计数器,栈顶指针为0
 53     memset(dfn,0,sizeof(dfn));    //结点搜索的次序编号数组为0,同时可以当是否访问的数组使用
 54     for(i=0;i<2*n;i++)   //枚举每个结点,搜索连通分量
 55         if(!dfn[i])  //未被访问
 56             Tarjan(i);  //则找i结点的连通分量
 57     for(int i=0;i<n;i++)
 58         if(belong[2*i]==belong[2*i+1])//相等表示两个数据在同一个集合之中
 59             return false;
 60     return true;
 61 }
 62 void init(){
 63     memset(head,-1,sizeof(head));   //邻接表的头结点数组初始化为-1
 64     memset(stack,0,sizeof(instack));
 65     memset(belong,-1,sizeof(belong));
 66     memset(low,0,sizeof(low));
 67     tot=0;
 68 }
 69 struct node{
 70     double x;
 71     double y;
 72     double len(node &a){
 73         return sqrt((x-a.x)*(x-a.x)+(y-a.y)*(y-a.y));
 74     }
 75     void in(){
 76         scanf("%lf%lf",&x,&y);
 77     }
 78 }P[1000];
 79 int cal(double mid){
 80     init();
 81    // cout<<mid<<" dafsdf"<<endl;
 82     for(int i=0;i<(n<<1);i++){
 83         for(int j=i+1;j<(n<<1);j++)if(j!=(i^1)){
 84             if(P[i].len(P[j])<mid){
 85                 Add(i,j^1);
 86                 Add(j,i^1);
 87             }
 88         }
 89     }
 90     if(Slove())return 1;
 91     return 0;
 92 }
 93 int main(){
 94     int m,i,x,y,c1,c2,a,b,c,d;
 95     while(~scanf("%d",&n))
 96     {
 97         for(int i=0;i<n;i++){
 98             P[i<<1].in();
 99             P[(i<<1)+1].in();
100         }
101         double l=0,r=10001;
102         double mid;
103         while(l<=r){
104             mid=(l+r)/2.0;
105             if(cal(mid)){
106                 l=mid+eps;
107             }
108             else{
109                 r=mid-eps;
110             }
111         }
112         printf("%.2lf\n",l/2.0);
113         /*
114         while(m--){
115             scanf("%d%d%d%d",&a,&b,&c,&d);
116             Add((a<<1)+c,(b<<1)+1-d);
117             Add((b<<1)+d,(a<<1)+1-c);
118             Add(2*a+c,2*b+(1-d));
119             Add(2*b+d,2*a+(1-c));
120         }*/
121         //求强连通分量
122         /*if(Slove())       //只有一个强连通分量,说明此图各个结点都可达
123             printf("YES\n");
124         else
125             printf("NO\n");*/
126     }
127     return 0;
128 
129 }
View Code

 

 poj 2723

n对钥匙对应n对锁  一对钥匙中,取了一个钥匙另一个就不能取,m扇门每个门上有两个种锁,打开的条件是至少一把锁被打开,问最多能打开多少门(门是有先后顺序的,前面的打开才能打开后面的)。

二分答案 

一对钥匙对应两种状态,一扇门取了一把钥匙x,则对应的x‘则不能取,那么存在x’类型的门必须由另一种钥匙打开。

  1 #include <iostream>
  2 #include <cstdio>
  3 #include <vector>
  4 #include <stack>
  5 #include <cstring>
  6 using namespace std;
  7 
  8 const int N=3000;
  9 const int inf=10000010;
 10 int dfn[N],low[N],dfs_clock;
 11 int belong[N],scc;
 12 
 13 stack<int>sta;
 14 int n,m;
 15 
 16 vector<int> g[N];
 17 
 18 int door[N][2];
 19 int mapping[N];
 20 int dfs(int u,int pre){
 21     dfn[u]=low[u]=++dfs_clock;
 22     int size=g[u].size();
 23     sta.push(u);
 24     for(int i=0;i<size;i++){
 25         int v=g[u][i];
 26         if(!dfn[v]){
 27             int lowv=dfs(v,u);
 28             low[u]=min(low[u],lowv);
 29         }else if(belong[v]==-1){
 30             low[u]=min(low[u],dfn[v]);
 31         }
 32     }
 33     if(low[u]==dfn[u]){
 34         int x;
 35         do{
 36             x=sta.top();sta.pop();
 37             belong[x]=scc;
 38         }while(x!=u);
 39         scc++;
 40     }
 41     return low[u];
 42 }
 43 int two_sat(){
 44 
 45     for(int i=0;i<2*n;i++)if(!dfn[i]){
 46         dfs(i,-1);
 47     }
 48     for(int i=0;i<n;i++){
 49         if(belong[i*2]==belong[i*2+1])return 0;
 50     }
 51     return 1;
 52 }
 53 void init(){
 54     dfs_clock=scc=0;
 55     for(int i=0;i<2*(n+1);i++)g[i].clear();
 56     memset(belong,-1,sizeof(int)*(2*n));
 57     memset(dfn,0,sizeof(int)*(2*n));
 58 }
 59 int ok(int mid){
 60     init();
 61     for(int i=1;i<=mid;i++){
 62         int x=door[i][0];
 63         for(int j=1;j<=mid;j++){
 64             if(door[j][0]==(x^1)){
 65                 g[x].push_back(door[j][1]);
 66             }
 67             if(door[j][1]==(x^1)){
 68                 g[x].push_back(door[j][0]);
 69             }
 70         }
 71         x=door[i][1];
 72         for(int j=1;j<=mid;j++){
 73             if(door[j][0]==(x^1)){
 74                 g[x].push_back(door[j][1]);
 75             }
 76             if(door[j][1]==(x^1)){
 77                 g[x].push_back(door[j][0]);
 78             }
 79         }
 80     }
 81     return two_sat();
 82 }
 83 int main()
 84 {
 85     int a,b;
 86     while(scanf("%d%d",&n,&m),n||m){
 87         for(int i=0;i<n;i++){
 88             scanf("%d%d",&a,&b);
 89             mapping[a]=2*i;
 90             mapping[b]=2*i+1;
 91         }
 92         for(int i=1;i<=m;i++){
 93             scanf("%d%d",&a,&b);
 94             door[i][0]=mapping[a];
 95             door[i][1]=mapping[b];
 96         }
 97         int l=0,r=m;int mid;
 98         while(l<=r){
 99             mid=(l+r)>>1;
100             if(ok(mid)){
101                 l=mid+1;
102             }
103             else{
104                 r=mid-1;
105             }
106         }
107         printf("%d\n",l-1);
108     }
109     return 0;
110 }
View Code

 

hdu 1816

大意与上一题相近,区别是本题中n对钥匙里可能有重复出现的(比如1和2,1和3,这时如果选了1,那么2,3都不能选),就不能形成2-sat的要求。直接看做每个钥匙选与不选。

  1 #include <iostream>
  2 #include <cstdio>
  3 #include <vector>
  4 #include <stack>
  5 #include <cstring>
  6 using namespace std;
  7 
  8 const int N=5000;
  9 const int inf=10000010;
 10 int dfn[N],low[N],dfs_clock;
 11 int belong[N],scc;
 12 
 13 stack<int>sta;
 14 int n,m;
 15 
 16 vector<int> g[N];
 17 
 18 int door[N][2];
 19 vector<int>key[N];
 20 int dfs(int u,int pre){
 21     dfn[u]=low[u]=++dfs_clock;
 22     int size=g[u].size();
 23     sta.push(u);
 24     for(int i=0;i<size;i++){
 25         int v=g[u][i];
 26         if(!dfn[v]){
 27             int lowv=dfs(v,u);
 28             low[u]=min(low[u],lowv);
 29         }else if(belong[v]==-1){
 30             low[u]=min(low[u],dfn[v]);
 31         }
 32     }
 33     if(low[u]==dfn[u]){
 34         int x;
 35         do{
 36             x=sta.top();sta.pop();
 37             belong[x]=scc;
 38         }while(x!=u);
 39         scc++;
 40     }
 41     return low[u];
 42 }
 43 int two_sat(){
 44 
 45     for(int i=0;i<4*n;i++)if(!dfn[i]){
 46         dfs(i,-1);
 47     }
 48     for(int i=0;i<2*n;i++){
 49         if(belong[i*2]==belong[i*2+1])return 0;
 50     }
 51     return 1;
 52 }
 53 void init(){
 54     dfs_clock=scc=0;
 55     for(int i=0;i<4*(n);i++)g[i].clear();
 56     memset(belong,-1,sizeof(int)*(4*n));
 57     memset(dfn,0,sizeof(int)*(4*n));
 58 }
 59 int ok(int mid){
 60     init();
 61     for(int i=0;i<2*n;i++){
 62         int size=key[i].size();
 63         for(int j=0;j<size;j++){
 64             g[i*2].push_back((key[i][j]*2)^1);
 65         }
 66     }
 67     for(int i=1;i<=mid;i++){
 68         int x=door[i][0],y=door[i][1];
 69         g[x*2^1].push_back(y*2);
 70         g[y*2^1].push_back(x*2);
 71 
 72     }
 73     for(int i=1;i<=mid;i++){
 74         int x=door[i][0];
 75         int size=key[x].size();
 76         for(int k=0;k<size;k++){
 77             int v=key[x][k];
 78             for(int j=1;j<=mid;j++){
 79                 if(door[j][0]==(v)){
 80                     g[x*2].push_back(door[j][1]*2);
 81                 }
 82                 if(door[j][1]==(v)){
 83                     g[x*2].push_back(door[j][0]*2);
 84                 }
 85             }
 86         }
 87         x=door[i][1];
 88         size=key[x].size();
 89         for(int k=0;k<size;k++){
 90             int v=key[x][k];
 91             for(int j=1;j<=mid;j++){
 92                 if(door[j][0]==(v)){
 93                     g[x*2].push_back(door[j][1]*2);
 94                 }
 95                 if(door[j][1]==(v)){
 96                     g[x*2].push_back(door[j][0]*2);
 97                 }
 98             }
 99         }
100     }
101     return two_sat();
102 }
103 int main()
104 {
105     int a,b;
106     while(scanf("%d%d",&n,&m),n||m){
107         for(int i=0;i<2*n;i++)key[i].clear();
108         for(int i=0;i<n;i++){
109             scanf("%d%d",&a,&b);
110             key[a].push_back(b);
111             key[b].push_back(a);
112         }
113         for(int i=1;i<=m;i++){
114             scanf("%d%d",&a,&b);
115             door[i][0]=a;
116             door[i][1]=b;
117         }
118         int l=0,r=m;int mid;
119         while(l<=r){
120             mid=(l+r)>>1;
121             if(ok(mid)){
122                 l=mid+1;
123             }
124             else{
125                 r=mid-1;
126             }
127         }
128         printf("%d\n",l-1);
129     }
130     return 0;
131 }
View Code

 

posted @ 2014-03-30 00:41  Mr.Youyu  阅读(282)  评论(0)    收藏  举报