2-sat总结


sat问题

关于定义,详见赵爽的《2-SAT解法浅析》,大概意思是有n个bool变量,一个全集为所有的他们以及他们的非,取其中的若干个变量或其非组成或的表达式,将这些表达式相与,求使与出的值为真的解集,为n-sat问题。

可证明,n>2时为NP完全问题,所以只讨论n=2的情况。


对于一个表达式a+b, a+b=!(!a*!b)  ,即,如果在解集中选择了a,那么必须选择!b,选择了b必然要选择!a

解法:构建包含2n个点的有向图,如果有a+b则在a和!b   b和!a间连接一条边。如果a和!a在一个强连通分量中,则无解。要求解集,只需要将原图缩点后反向建图,然后染色,具体染色方法是将遇到的第一个没有颜色的点染成红色,与它矛盾的点染成蓝色,如此循环,所有的红色的点的集合就是解集。

poj2723   题意:有m层楼,每层楼有2把锁,有n组钥匙,每组2把,用的时候只能用一组中的一把(另一把不能再用),给出钥匙的分组,给出每层楼需要用哪两把钥匙开,开启楼层的时候按顺序从前往后依次来开,一层楼只需要一把钥匙就能打开,求最后能开多少把锁。


2sat+二分

一组的钥匙相当于原变量和非变量,一层楼的锁相当于元素的或,因为只需要一把钥匙就可以,所以为(a+b)   因为是从前往后开,只需要二分楼层就可以,根据楼层建图来判断是否满足题意。

代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<vector>
#include<stack>
#include<string>
#include<map>
#include<cmath>
using namespace std;

const int maxn=4200;
const int maxm=10010;
struct node{
    int v,next;
}e[maxm];
int head[maxn],dfn[maxn],low[maxn],id[maxn],sta[maxn],match[maxn];//match数组表示在一个组的两把钥匙
int x[maxn],y[maxn],vis[maxn];
int n,m,cnt,top,indx,tot;

void add(int u,int v){
    e[tot].v=v;
    e[tot].next=head[u];
    head[u]=tot++;
}

void tarjan(int u){        //tarjan缩点不解释
    low[u]=dfn[u]=++indx;
    sta[++top]=u;
    vis[u]=1;
    int p;
    for(int i=head[u];i!=-1;i=e[i].next){
        int v=e[i].v;
        if(!dfn[v]){
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(!id[v]) low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u]){
        cnt++;
        int p;
        do{
            p=sta[top];
            id[p]=cnt;
        }while(sta[top--]!=u);
    }
}
int solve(int mid){     //mid前的楼层能打开,建图
    indx=0;
    top=-1;
    cnt=tot=0;
    memset(dfn,0,sizeof(dfn));
    memset(id,0,sizeof(id));
    memset(head,-1,sizeof(head));
    memset(vis,0,sizeof(vis));
    for(int i=0;i<mid;i++){
        add(x[i],match[y[i]]);        //(a+b) a->!b    b->!a
        add(y[i],match[x[i]]);
    }
    for(int i=0;i<2*n;++i){
        if(!dfn[i]) tarjan(i);
    }
    for(int i=0;i<n;++i)
        if(id[i]==id[match[i]]) return false;    // 两个点在同一个强连通分量,则无法打开
    return true;
}

int main()
{
    int u,v;
    ios::sync_with_stdio(0);
    while(cin>>n>>m,m+n){
        for(int i=0;i<n;++i){
            cin>>u>>v;
            match[u]=v;       //a    !a
            match[v]=u;
        }
        for(int i=0;i<m;++i)
            cin>>x[i]>>y[i];
        int l=0,r=m+1,mid;
        while(l+1<r){          //二分
            mid=(l+r)/2;
            if(solve(mid)) l=mid;
            else r=mid;
        }
        cout<<l<<endl;
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值