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