题目大意:
给出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;
}