URAL 1382 Game with Cards (2-SAT)

题目大意:

给出n行描述,每行的两句话都是一句话是对的,一句话是错的,要求输出最终的牌号和人的编号匹配的情况下,每个人说的话中是第一句对还是第二句对,题目保证一定有解


大致思路:

对于每个人说的话可以视作一个命题,我们将这些命题编号从1到n; 对于命题 xi, 有两种状态,一种是前一句话对,一种是后一句话对,在2-SAT算法中将这两种状态分别用结点编号为 2*i 和 2*i + 1 的点来表示

那么对于每个命题xi,由于2-SAT算法本身保证了两个结点不会同时被染色,也就不会出现矛盾的情况,那么对于两个不同的命题 xi 和 xj 就需要看是否存在矛盾来进行连边。


对于命题 xi 和 xj :

            2*i         2*i + 1

xi      i -> a[i]     b[i] -> c[i]

xj      j -> a[j]     b[j] -> c[j]

            2*j           2*j + 1


由于i != j 当 a[i] == a[j] 时,由于两者同时出现将会产生矛盾, 需要连边  2*i -> 2*j + 1    和  2*j  -> 2*i + 1  即如果 xi 为假(结点2*i被染色),那么 xj 为真即需要染色 2*i + 1,另外一个同理。

当 i == b[i] || a[i] == c[j] 时, 连边 2*i -> 2*j   和 2*j + 1 -> 2*i + 1

当b[i] == j || c[i] == a[j] 时, 连边 2*i + 1 -> 2*j + 1 和 2*j -> 2*i

当 b[i] == b[j] || c[i] == c[j] 时, 连边 2*i + 1 -> 2*j 和 2*j + 1 -> 2*i

然后用2-SAT算法跑一遍就可以了

这样子对于命题 xi 如果结点 2*i 被标记就说明第一句话为真,否则说明第二句话为真

代码如下:

Result  :  Accepted     Memory  :  7941 KB     Time  :  46 ms

/*
 * Author: Gatevin
 * Created Time:  2014/7/23 19:28:14
 * File Name: test.cpp
 */
#include<iostream>
#include<sstream>
#include<fstream>
#include<vector>
#include<list>
#include<deque>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<ctime>
#include<iomanip>
using namespace std;
const double eps(1e-8);
typedef long long lint;

#define maxn 1010

int n;
int a[maxn], b[maxn], c[maxn];
struct TwoSAT
{
    int n;
    vector <int> G[maxn*2];
    bool mark[maxn*2];
    int S[maxn*2], c;
    
    bool dfs(int x)
    {
        if(mark[x^1]) return false;//结点x的对立点已经标为成立
        if(mark[x]) return true;//x已经被访问过,说明继续下去已经可以保证是对的了,不需要重复标记
        mark[x] = true;
        S[c++] = x;
        for(unsigned int i = 0; i < G[x].size(); i++)
            if(!dfs(G[x][i])) return false;//递推x结点能得到的结果,进行标记
        return true;
    }
    
    void init(int n)
    {
        this->n = n;
        for(int i = 0; i < n*2; i++)
        {
            G[i].clear();
        }
        memset(mark, 0, sizeof(mark));
    }
    
    //x = xval or y = yval
    void add_clause(int x, int xval, int y, int yval)
    {
        x = x*2 + xval;
        y = y*2 + yval;
        G[x^1].push_back(y);
        G[y^1].push_back(x);
    }
    
    bool solve()
    {
        for(int i = 0; i < n*2; i += 2)
        {
            if(!mark[i] && !mark[i + 1])
            {
                c = 0;
                if(!dfs(i))
                {
                    while(c > 0) mark[S[--c]] = false;//dfs(i)如果失败就擦掉之前实验标记的点
                    if(!dfs(i + 1)) return false;//换dfs(i = 1)上如果依旧失败说明无解
                }
            }
        }
        return true;
    }
};

TwoSAT answer;

int main()
{
    scanf("%d",&n);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d %d %d", &a[i], &b[i], &c[i]);
    }
    answer.init(n);
    for(int i = 1; i <= n; i++)
    {
        for(int j = i + 1; j <= n; j++)
        {
            if(a[i] == a[j]) answer.add_clause(i - 1, 1, j - 1, 1);//对应的连边方式,这里编号需要减一,方便异或运算
            if(i == b[j] || a[i] == c[j]) answer.add_clause(i - 1, 1, j - 1, 0);
            if(j == b[i] || a[j] == c[i]) answer.add_clause(i - 1, 0, j - 1, 1);
            if(b[i] == b[j] || c[i] == c[j]) answer.add_clause(i - 1, 0, j - 1, 0);
        }
    }
    bool flag = answer.solve();
    if(flag)
    {
        for(int i = 0; i < n*2; i += 2)
        {
            if(answer.mark[i]) printf("1");
            else printf("2");
            if(i != 2*n - 2) printf(" ");
        }
    }
    return 0;
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值