http://www.javashuo.com/article/p-dfbzigjb-eb.htmlphp
2-sat是k-sat问题中k==2时的一种状况,,(废话qaq,,html
当k大于等于3时是npc问题,,因此通常都是问的2-sat,,ios
这种题的大概形式是: 对于给定的n对的点,要求每一对都只能选择一个,而且其中还有一些限制条件,好比说选了u就不能选择v等等,,c++
而后问你有没有可行解,,,算法
解决这类问题通常是用 染色法(求字典序最小的解) 和 强连通份量法(拓扑排序只能获得任意解),,spa
u->v
(选择u就不能选择v)这样的限制条件能够用它的逆否命题来转换为:u->v'
(选择u就必须选v')以及 v->u'
(选择v就必须选u')这个算法的大体思路就是遍历每一对点的两种状况:选p或者选p',,,code
而后一直从p的下一个尝试下去,,中间如果碰到不能避免的不知足题意的选择时,证实这条路下来的尝试时不行的,,从新选择,,一直下去。。。也就是一个深搜的过程,,时间复杂度大概是 \(O(nm)\),,htm
能够看看这篇博客,,blog
这个算法的流程为:排序
时间复杂度大概为 \(O(m)\),,就是难写,,并且不能输出字典序小的解,,,
这道模板题,,让输出的书字典序小的解,,,只能用第一种方法了,,,
题意和上面那个百度文库的例题同样,,,
//#include <bits/stdc++.h> #include <iostream> #include <cstdio> #include <cstdlib> #include <string.h> #include <vector> #include <queue> #include <functional> #define aaa cout<<233<<endl; #define endl '\n' #define pb push_back using namespace std; typedef long long ll; typedef unsigned long long ull; const int inf = 0x3f3f3f3f;//1061109567 const ll linf = 0x3f3f3f3f3f3f3f; const double eps = 1e-6; const double pi = 3.14159265358979; const int maxn = 1e5 + 5; const int maxm = 2e5 + 5; const int mod = 1e9 + 7; inline ll read() { char c = getchar(); int x = 0, f = 1; while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();} while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); return x * f; } //2sat_kuangbin struct edge { int to, next; }edge[maxn]; int head[maxn], tot; void init() { 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++; } bool vis[maxn]; int s[maxn], top; bool dfs(int u) { if(vis[u^1])return false; //若是这个点p的对立面p'选了,那么这个点就不选 if(vis[u]) return true; //若是这个点已经选了,就不从这个点继续向下找了 vis[u] = true; //这个点p没选而且对立面p'没选的状况下,选择这个点,而且尝试从这个点寻找可能的解法 s[top++] = u; //把这个可能的一种状况压栈,保存 for(int i = head[u]; ~i; i = edge[i].next) if(!dfs(edge[i].to)) return false; //尝试全部与点u相连的点v,若是从点v出发的尝试不可行时不选 return true; } bool two_sat(int n) { memset(vis, false, sizeof vis); //vis[i]标记那些点要选 for(int i = 0; i < n; i += 2) { if(vis[i] || vis[i^1])continue;//若是这一对点有一个选过就尝试下一对的点 top = 0; if(!dfs(i)) //若是从点i出发的尝试不行,就将栈中全部这条可能的路径上的点标记为未选 { while(top)vis[s[--top]] = false; if(!dfs(i^1))return false;//若是点i的对立面i'都不行的话,证实没法找到这样一条可行解,使得每一对点仅选择一个而且知足对应的限制 } } return true; } int main() { // freopen("233.in" , "r" , stdin); // freopen("233.out" , "w" , stdout); // ios_base::sync_with_stdio(0); // cin.tie(0);cout.tie(0); int n, m, u, v; while(scanf("%d%d", &n, &m) != EOF) { init(); for(int i = 1; i <= m; ++i) { scanf("%d%d", &u, &v); --u;--v; //点的编号从0开始,方便使用p^1来表示p的对立面 addedge(u, v^1);//建图,限制条件u->v(选择u就不能选择v)等价于u->v' && v->u' (选择u必须选额v' 和 选择v就必须选择u') addedge(v, u^1); } if(two_sat(2 * n)) //存在解时 { for(int i = 0; i < 2 * n; ++i) if(vis[i]) //将最后字典序最小的可行解输出 printf("%d\n", i + 1); } else printf("NIE\n"); } return 0; }
强连通份量的方法明天,啊不白天再说吧,,,溜了溜了
(loading)