HDU - 3062 Party 2-SAT问题 tarjan 模板

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

SAT是适定性(Satisfiability)问题的简称 。一般形式为k-适定性问题,简称 k-SAT。 
当k>2时,k-SAT是NP完全的。因此一般讨论的是k=2的情况,即2-SAT问题。 
2-SAT,简单的说就是给出n个集合,每个集合有两个元素, 
已知若干个 < a,b >,表示a与b矛盾(其中a与b属于不同的集合)。 
然后从每个集合选择一个元素,一共选n个两两不矛盾的元素。 
显然可能有多种选择方案,一般题中只需要求出一种即可。

2-SAT问题

构造一个有向图G,每个变量xi拆成两个点2i和2i+1 
分别表示xi为假,xi为真 
那么对于“xi为真或xj为假”这样的条件 
我们就需要连接两条边 
2*i —>2*j(表示如果i为假,那么j必须是假) (重点)
2*j+1—>2*i+1(表示如果j为真,那么i必须是真) 
这就有点像推导的过程 
实际上每一个限制条件都会对应两条“对称”的边

接下来逐考虑每个没有赋值的变量,设为x 
我们先假定x为假(为什么一定是假,约定俗成了) 
之后沿着从ta出发的有向边进行标记 
如果在标记过程中,发现有一个点的两种状态都被标记过了 
那么我们之前的假设就被推翻了 
需要改成x为真,重新标记 
如果发现无论这个点赋值成真还是假,都会引起矛盾 
可以证明这个2-SAT无解

这个问题就是个模板题,加一下边就好了

链接:hdu - 3062

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <set>
#include <map>
#include <stack>
#include <queue>
#define INF 0x3f3f3f3f

using namespace std;

const int inf = 0x3f3f3f3f;
const int maxn = 100005;
const int maxm = 4000005;
//a<<1 和 (a<<1) + 1。a<<1表示妻子,(a<<1) + 1表示丈夫
//连接某边是为了推出矛盾。x->y表示选x则必须选y
//注意方向
struct node {
    int s, t;
    int nxt;
} e[maxm];

int n, m;
int idx, ans, tp, cont;
int dfn[maxn], vis[maxn], low[maxn], head[maxn], st[maxn], belong[maxn];

void init() {
    cont = 0;
    memset(head, -1, sizeof(head));
    memset(vis, 0, sizeof(vis));
    memset(dfn, 0, sizeof(dfn));
    idx = tp = ans = 0;
}
 
void add(int s, int t) {
    e[cont].s = s;
    e[cont].t = t;
    e[cont].nxt = head[s];
    head[s] = cont++;
}

void tarjan(int u) {
    dfn[u] = low[u] = ++idx;
    vis[u] = 1;
    st[++tp] = u;
    int v ;
    for(int i = head[u]; i != -1; i = e[i].nxt) {
        v = e[i].t ;
        if(!dfn[v]) {
            tarjan(v) ;
            low[u] = min(low[u],low[v]);
        }
        else if(vis[v])
            low[u] = min(low[u],dfn[v]);
    }
    if(dfn[u] == low[u]) {
        ans++;
        while(1) {
            v = st[tp--];
            vis[v] = 0;
            belong[v] = ans;
            if(v == u)
                break;
        }
    }
}

bool _2sat() {
    for(int i = 0; i < 2 * n; i++)
        if(!dfn[i])
            tarjan(i) ;
    for(int i = 0; i < n; i++)
        if(belong[2 * i] == belong[(2 * i) ^ 1])//矛盾
            return false;
    return true;
}
 
int main()
{
    int a, b, c, d;
    while(~scanf("%d %d",&n, &m)) { 
        init();
        for(int i = 0; i < m; i++) {
            scanf("%d %d %d %d", &a, &b, &c, &d);
            //int u, v;
            //u = a*2+c; v = b*2+d;
            //add(u, v^1); add(v, u^1);
            if(c == 0 && d == 0) {//两个妻子
                add(a << 1, (b << 1) + 1);
                add(b << 1, (a << 1) + 1);
            }
            else if(c == 0 && d == 1) {//妻子和丈夫 
                add((a << 1), (b << 1));
                add((b << 1) + 1, (a << 1) + 1);
            }
            else if(c == 1 && d == 0) {//丈夫和妻子 
                add((a << 1) + 1, (b << 1) + 1);
                add(b << 1, a << 1);
            }
            else if(c == 1 && d == 1) {//两个丈夫有矛盾 
                add((a << 1) + 1, b << 1);
                add((b << 1) + 1, a << 1);
            }
        }
        if(_2sat())
            printf("YES\n");
        else
            printf("NO\n");
    }
    return 0 ;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值