2-SAT

//参考 https://wenku.baidu.com/view/afd6c436a32d7375a41780f2.html 

//2 - SAT 问题 : 给定N个组,每个组有两个点,某些点不相容,从每个组选一个点问怎么选出相容的N个点 


//得到字典序最小的解:染色法
//HDU 1814
//复杂度O(VE)

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

const int maxn = 20000 + 10 ;
const int maxm = 1e5 + 10 ;
struct Edge{
    int to , next ;
}edge[maxm];

int head[maxn ] , tot ;
void ini(){
    tot = 0 ;
    memset( head , -1 ,sizeof( head )) ;
}
void addedge( int u , int v ){
    edge[tot].to = v ; edge[tot].next = head[u] ; head[u] = tot ++ ;
}

//染色标记,为true表示选择 
bool vis[maxn ] ;
//栈 
int S[maxn] , top ;

bool dfs( int u ){
    //如果u和他的反点被同时选中,那么这个图无解 
    if( vis[u ^ 1] )
        return false ;
    //如果有环且不经过u的反向点,那么该解成立 
    if( vis[u])
        return true ;
    vis[u] = true ;
    S[top ++ ] = u ;
    for( int i = head[u] ; i != -1 ;i= edge[i].next ){
        if( !dfs(edge[i].to ) )
            return false ;
    }
    //u开始的有向路径无环得到一个可行的解 
    return true ;
}
bool Twosat( int n ){
    memset( vis , false , sizeof( vis )) ;
    //找字典序最小的解 
    for( int i = 0 ;i<n ;i+= 2){
        if( vis[i] || vis[i^1] )
            continue ;
        top = 0 ;
        //以i为起点选择,如果i的反点在同一个环中, 
        if( !dfs(i) ){
            //回溯 
            while( top ) 
                vis[S[ -- top ]] = false ;
            //判定以i的反点开始是否存在可行解 
            if( !dfs( i ^ 1 ))
            //不存在返回false 
                return false ;
        }
    } 
    //整个图存在可行解 
    return true ;
} 

int main(){
    int n , m ;
    int u , v ;
    while( scanf("%d%d" , & n , & m ) == 2 ){
        ini() ;
        while( m -- ){
            scanf("%d%d" , & u , & v) ;
            u -- , v -- ;
            //从0开始标号,异或值表示反向点或者反向边 
            addedge( u , v^1) ;
            addedge( v , u^1) ; 
        }
        //判断该2-sat问题是否有解 
        if( Twosat( 2* n )){
            for( int i = 0 ;i< 2* n ;i++)
            //字典序最小的解 
                if(vis[i] )
                    printf("%d\n" , i + 1 ) ;
        }
        else printf("NIE\n") ;
    }
    return 0  ;
}
//2 - sat 问题
//求任意一组解:强连通缩点 + 拓扑排序
//POj 3648


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

const int maxn = 1e4 + 10 ;
const int maxm = 1e5 + 10 ;

struct Edge{
    int to , next ;
}edge[maxm];

int head[maxn] , tot ;
void ini(){
    tot = 0 ;
    memset( head , -1 , sizeof( head )) ;
} 

void addedge( int u , int v){
    edge[tot].to = v ; edge[tot].next = head[u] ; head[u] = tot ++;
}

int Low[maxn] , DFN[maxn] , Stack[maxn] , Belong[maxn] ;
int Index , top ;
int scc ;
bool Instack[maxn] ;
int num [maxn] ;

void Tarjan( int u ){
    int v ;
    Low[u] = DFN[u] = ++ Index ;
    Stack[top ++ ] = u ;
    Instack[u] = true ;
    for( int i = head[u] ; i!= -1 ;i = edge[i].next ){
        v = edge[i].to ;
        if( !DFN[v]){
            Tarjan( v ) ;
            if( Low[u] > Low[v] )
                Low[u] = Low[v] ;
        }
        else if( Instack[v] && Low[u] > DFN[v]){
            Low[u] = DFN[v] ;
        }
    } 
    if( Low[u] == DFN[u]){
        scc ++ ;
        do {
            v = Stack[ -- top ] ;
            Instack[v] = false ;
            Belong[v] = scc ;
            num[ scc ] ++; 
        }
        while( v != u ) ;
    }
}
//求连通分量 
bool  solveable( int n ){
    memset( DFN , 0 , sizeof( DFN )) ;
    memset( Instack , false , sizeof( Instack )) ;
    memset( num , 0 , sizeof( num )) ;
    Index = scc = top = 0 ;
    for( int i = 0 ;i<n ;i++){
        if(!DFN[i])
            Tarjan(i ) ;
    }
    for(int i = 0 ;i<n; i++)
        if( Belong [i] == Belong[ i^ 1 ])
            return false ;
    return true ;
}

queue<int > q1 ;
//缩点后的逆向DAG图 
vector< vector<int > > dag ;
//染色,是R的选择 
char color[maxn] ;
//入度 
int indeg[maxn] ;
int cf[maxn] ;

void solve( int n ){
    dag.assign( scc + 1 , vector<int>() ) ;
    memset( indeg , 0 ,sizeof( indeg )) ;
    memset( color , 0 ,sizeof( color )) ;
    //缩点 
    //n个点的边 
    for( int u = 0 ;u< n ;u ++){
        for( int i = head[u] ; i!= -1 ;i= edge[i].next ){
            int v = edge[i].to ;
            if( Belong[u] != Belong[v]){
                dag[Belong[v]].push_back( Belong[u ]) ;
                indeg[Belong[u]] ++ ;
            }
        }
    }
    for( int i = 0 ;i<n ;i+= 2 ){
        //i的对立点和i不能再同一个连通分量里面 
        cf[Belong[i] ] = Belong[i^1 ] ;
        cf[Belong[i ^ 1 ] ] = Belong[i] ;
    }


    while( !q1.empty( ) )
        q1.pop() ;
    for(int i = 1 ;i<= scc ; i++){
        if( indeg[i] == 0 )
            q1.push( i ) ;
    }
    while( !q1.empty() ){
        int u = q1.front() ;
        q1.pop() ;
        if( color[u] == 0 ){
            //u点选择,则他的对立点不能选择 
            color[u] = 'R' ;
            color[ cf[u] ] = 'B'  ;
        }
        //拓扑排序 
        int sz = dag[u].size() ;
        for( int i = 0 ;i<sz ;i++){
            indeg[ dag[u][i] ] -- ;
            if( indeg[dag[u][i] ] == 0 )
                q1.push( dag[u][i] ) ;
        }
    }
}
int change( char s[] ){
    int ret = 0 ;
    int i = 0 ;
    while( s[i] >= '0' && s[i] <= '9'){
        ret *= 10 ;
        ret += s[i] - '0' ;
        i ++ ; 
    } 
    if( s[i] == 'w')
        return 2 * ret ;
    else
        return 2 * ret + 1 ; 
}
int main(){
    int n , m ;
    char s1[10] , s2[10 ] ;
    while( scanf("%d%d" , & n , & m ) == 2 ){
        if( n == 0 && m == 0 )
            break ;
        ini() ;
        while( m -- ){
            scanf("%s%s" , s1 , s2 ) ;
            int u = change( s1 ) ;
            int v = change( s2 ) ;
            addedge( u^1 , v ) ;
            addedge( v^1 , u ) ;
        } 
        addedge( 1 , 0 ) ;
        if( solveable( 2 * n )){
            solve( 2* n ) ;
            for( int i = 1 ;i<n ;i++){
                if( color[Belong [2* i ]] == 'R')
                    printf("%dw" , i ) ;
                else    
                    printf("%dh" , i ) ;
                if( i < n - 1 )
                    printf(" " ) ;
                else 
                    printf("\n") ;
            }
        }
        else
            printf("bad luck\n") ;
    }
    return 0 ;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值