2011.08.08

2-SAT问题

摘自:http://www.cppblog.com/Yuan/archive/2010/08/04/122209.html

2 – SAT 就是2判定性问题,是一种特殊的逻辑判定问题。

例,n对东西,每对只能选一个(i0或i1),不能不选。即:A or _A = 1 , A xor _A = 1,还存在一些约束关系(i0,j0),表示i0不能跟j0一起选。那需连边

i0-> j1 如果选i0的话必须选j1

j0-> i1如果选j0的话必须选i1

      表示了一种递推的关系:选哪个必选哪一个

      一般题目给的都是一对一对的东西,二选一,不能不选。本身那几对东西是没有关系的,然后题目会定义一些规则,或者我们自己加入条件(如二分的参数),使对与对之间有了一些约束关系: 1)选a必选b a->b

                   2)a必选 _a->a

       对这个新图求SCC,同一SCC的要么全选,要么都不选。

      如果发现a,_a在同一SCC,表明矛盾了。

      不矛盾的话,对缩点后的图按照自底向上选择一个还未被标记的点,标记选上,然后把它的对立点及其前代都删除。

      (选择一个点要把它及其所有后代都选上。不选一个点,要把它及其前代都不选。)

算法流程:

      1、建图,求SCC。若某一个点,a与_a在同一个SCC,则无解,退出;

      2、对求得的缩点,用原来的反图建一个DAG;

      3、TopoSort得到一个原图的自底向上的序列,点是缩点后的SCC,没必要原来的每个点;

      4、从左往右,对整个序列的点,如果未标记删除,就标记选它,同时把这个SCC里的所有原来的点的对立点及其各自的后代(因为是反图)都标记删除;

      5、重复上面过程,最后被标记选上的SCC内的点都是选择的点。其个数=n

      虽然被删除的点也是n个,但是他们可能会存在冲突关系。而标记选上的点间是相容的。


HDU  3062  Party

        题意:中文原题TSO:有n对夫妻被邀请参加一个聚会,因为场地的问题,每对夫妻中只有1人可以列席。在2n 个人中,某些人之间有着很大的矛盾(当然夫妻之间是没有矛盾的),有矛盾的2个人是不会同时出现在聚会上的。有没有可能会有n 个人同时列席?

        思路:2-SAT判定问题,找强联通分量以后判定是否存在一对夫妻属于同一个强联通分量,有则不可能,输出"NO",否则输出"YES"。

#include <iostream>
#include <vector>
#include <stack>
#include <cstdio>
#include <cstring>
using namespace std;

const int MAXN = 3009;
vector<int> g[MAXN] , gt[MAXN] , tree[MAXN];         // g正图 , gt 逆图 , tree缩点逆图
int n , m , cnt , ID , id[MAXN] , tree_cnt , order[MAXN] , indegree[MAXN] , color[MAXN];
bool used[MAXN] , used_tree[MAXN][MAXN];

void init()
{
        for(int i=0; i < 2*n; i++) {
                g[i].clear();
                gt[i].clear();
        }
}

void build_graph()// 建图,i << 1 与 (i << 1) + 1 相斥
{
        int u , v , uu , vv , c1 , c2;
        for(int i=0; i < m; i++){
                scanf("%d%d%d%d",&u,&v,&c1,&c2);
                if(c1 == 0){
                        u = u * 2;
                        uu = u + 1;
                }
                else{
                        u = 2 * u + 1;
                        uu = u - 1;
                }
                if(c2 == 0){
                        v = 2 * v;
                        vv = v + 1;
                }
                else{
                        v = 2 * v + 1;
                        vv = v - 1;
                }
                g[u].push_back( vv );
                gt[vv].push_back( u );
                g[v].push_back( uu );
                gt[uu].push_back( v );
        }
}
void dfs1( int u )
{
        vector<int>::iterator v;
        used[u] = true;
        for(v = g[u].begin(); v != g[u].end(); v++)
                if(!used[*v])
                        dfs1(*v);
        order[cnt++] = u;
}
void dfs2( int u )
{
        vector<int>::iterator v;
        used[u] = true;
        id[u] = ID;
        for(v = gt[u].begin(); v != gt[u].end(); v++)
                if(!used[*v])
                        dfs2(*v);
}
void scc() // 缩点
{
        memset(used , 0 , sizeof(used));
        cnt = 0;
        for(int i=0; i < 2*n; i++)
                if(!used[i])
                        dfs1(i);
        memset(used , 0 , sizeof(used));
        ID = 0;
        for(int i=cnt - 1; i >= 0 ; i--)
                if(!used[ order[i] ]){
                        ID ++;
                        dfs2( order[i] );
                }
}
bool sat_judge()  //2-sat判定
{
        for(int i=0; i < 2*n; i+=2)
                if(id[i] == id[i+1])
                        return false;  //无解
        return true;
}
void solve()
{
        build_graph();
        scc();
        if(!sat_judge()) puts("NO");
        else puts("YES");
        return;
}
int main()
{
        while(scanf("%d %d" , &n , &m ) != EOF) {
                if(n == 0 && m == 0) break;
                init();
                solve();
        }
        return 0;
}


POJ   3683  Priest John's Busiest Day

        题意:神父要在一天中给n对新人举行某仪式,给出每对新人允许的时间段和仪式所花费的时间,神父可以在这个时间段头或尾为新人举行仪式。问如果神父可以安排时间完成所有仪式则输出YES,并给出一组可行方案,否则输出NO。

        解法:判定每一对新人的允许时间段,有重叠部分则冲突,用2-SAT模型解。

#include<cstring>
#include<cstdio>
#include<vector>
#include<stack>
using namespace std;
const int MAXN = 3001;
vector<int> g[MAXN] , gt[MAXN] , tree[MAXN];         // g正图 , gt 逆图 , tree缩点逆图
int n , m , cnt , ID , id[MAXN] , tree_cnt , order[MAXN] , indegree[MAXN] , color[MAXN];
bool used[MAXN] , used_tree[MAXN][MAXN];
int t[MAXN][2];
void init()
{
        for(int i=0; i < 2*n; i++) {
                g[i].clear();
                gt[i].clear();
        }
        return;
}
void build_graph()// 建图,i << 1 与 (i << 1) + 1 相斥
{
    int a,b,c,d,e;
    for(int i=1;i<=n;i++){
        scanf("%d:%d %d:%d %d",&a,&b,&c,&d,&e);
        t[2*i-2][0]=a*60+b;t[2*i-2][1]=a*60+b+e;
        t[2*i-1][0]=c*60+d-e;t[2*i-1][1]=c*60+d;
    }
    for(int i=0;i<2*n;i++)
        for(int j=i+1;j<2*n;j++){
            if(i%2==0 && j==i+1) continue;
            if(!(t[i][0]>=t[j][1] || t[j][0]>=t[i][1])){
                g[i].push_back( j^1 );
                gt[j^1].push_back( i );
                g[j].push_back( i^1 );
                gt[i^1].push_back( j );
            }
        }
}
void dfs1( int u )
{
        vector<int>::iterator v;
        used[u] = true;
        for(v = g[u].begin(); v != g[u].end(); v++)
                if(!used[*v])
                        dfs1(*v);
        order[cnt++] = u;
}
void dfs2( int u )
{
        vector<int>::iterator v;
        used[u] = true;
        id[u] = ID;
        for(v = gt[u].begin(); v != gt[u].end(); v++)
                if(!used[*v])
                        dfs2(*v);
}
void scc() // 缩点
{
        memset(used , 0 , sizeof(used));
        cnt = 0;
        for(int i=0; i < 2*n; i++)
                if(!used[i])
                        dfs1(i);
        memset(used , 0 , sizeof(used));
        ID = 0;
        for(int i=cnt - 1; i >= 0 ; i--)
                if(!used[ order[i] ]){
                        ID ++;
                        dfs2( order[i] );
                }
}
bool sat_judge()  //2-sat判定
{
        for(int i=0; i < 2*n; i+=2)
                if(id[i] == id[i+1])
                        return false;  //无解
        return true;
}
void build_tree()  //缩点成树 , 建立逆图
{
        memset(used_tree , 0 , sizeof(used_tree));
        memset(indegree , 0 , sizeof(indegree));
        for(int i=1; i <= ID; i++) tree[i].clear();
        for(int i=0; i < 2*n; i++)
                for(vector<int>::iterator j=g[i].begin(); j != g[i].end(); j++)
                        if(!used_tree[ id[*j] ][ id[i] ] && id[*j] != id[i]) {
                                indegree[ id[i] ] ++;
                                tree[ id[*j] ].push_back( id[i] );
                                used_tree[ id[*j] ][ id[i] ] = true;
                        }
}
void topsort()//拓扑排序
{
        stack<int> S;
        for(int i=1; i <= ID; i++)
                if(indegree[i] == 0)
                        S.push( i );
        tree_cnt = 0;
        while( !S.empty() ) {
                int u = S.top();
                S.pop();
                order[ tree_cnt ++ ] = u;  //记录反向拓扑排序的序列
                vector<int>::iterator v;
                for(v = tree[u].begin(); v != tree[u].end(); v++) {
                        indegree[*v] --;
                        if(indegree[*v] == 0)
                                S.push( *v );
                }
        }
}
void dfs_tree( int u )
{
        vector<int>::iterator v;
        color[u] = 2;
        for(v = tree[u].begin(); v != tree[u].end(); v++)
                if(!color[*v])
                        dfs_tree(*v);
}
void tree_color()//对树进行染色
{
        memset(color , 0 , sizeof(color));
        for(int i=0; i < tree_cnt; i++)
                if(!color[ order[i] ]){
                        color[ order[i] ] = 1;
                        for(int j=0; j < 2*n; j++) {
                                if(id[j] == order[i]) {
                                        int tmp ;
                                        if(j & 1) tmp = j - 1;
                                        else tmp = j + 1;
                                        tmp = id[ tmp ];
                                        if(color[tmp]) continue;
                                        color[tmp] = 2;
                                        dfs_tree( tmp );//对前向边染色
                                }
                        }
                }
}
void answer()
{
        int sign = color[ id[0] ]; //得出结果
        for(int i=0; i < n*2; i++){
                if(color[id[i]] == sign){
                    int h1,h2,m1,m2;
                    h1=t[i][0]/60;m1=t[i][0]%60;
                    h2=t[i][1]/60;m2=t[i][1]%60;
                    if(h1<10)printf("0%d:",h1);
                    else printf("%d:",h1);
                    if(m1<10)printf("0%d ",m1);
                    else printf("%d ",m1);
                    if(h2<10)printf("0%d:",h2);
                    else printf("%d:",h2);
                    if(m2<10) printf("0%d\n",m2);
                    else printf("%d\n",m2);
                }
        }
        return ;
}
void solve()
{
        build_graph();
        scc();
        if(!sat_judge()){
            printf("NO\n");
            return;
        }
        printf("YES\n");
        build_tree();
        topsort();
        tree_color();
        answer();
}

int main()
{
    freopen("in","r",stdin);
    freopen("out","w",stdout);
    while(EOF!=scanf("%d",&n)){
        if(n==0) break;
        init();
        solve();
    }
    return 0;
}


       


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值