11.HDU 1814 Peaceful Commission
题意:
建图老题意,表示有n对人要出席一个会议,但是有m个恶劣的关系不允许同时出现。
但是不一样的地方就是需要输出可行解的最小序列
思路:
让你判断一下还是很简单的,但是输出最小序列真的让博主感到很无力,想了很长时间也没有一个快速的方法。看了题解是说暴搜索可解,我这里說一下暴搜思路。
先将所有节点染成白色(初始化),再进行遍历。如果已经染成红色或者黑色的节点就跳过,否则,我们先尝试将第一个节点染成红色,再进行暴力dfs缩点,暴力过程中,优先让第一个节点染成红色,第二个染成黑色。如果没有任何冲突,则表示缩点成功。否则尝试将第二个节点染成红色,再缩点,如果还不行,就说明无解了。
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <iterator>
#include <map>
#include <queue>
#include <set>
#include <vector>
#define R 1
#define B 2
#define W 0
using namespace std;
const int maxn = 16005;
int n, m, idx, cnt;
int head[maxn], color[maxn], tmp[maxn];
struct node {
int to;
int nxt;
} edges[maxn << 4];
void init()
{
n <<= 1;
for (int i = 0; i <= n; i++)
head[i] = -1;
}
void addEdge(const int& u, const int& v)
{
edges[idx].to = v;
edges[idx].nxt = head[u];
head[u] = idx++;
}
bool dfs(int u)
{
if (color[u] == R)
return true;
if (color[u] == B)
return false;
color[u] = R, color[u ^ 1] = B;
tmp[cnt++] = u;
for (int id = head[u]; ~id; id = edges[id].nxt) {
if (!dfs(edges[id].to))
return false;
}
return true;
}
bool solve()
{
memset(color, 0, sizeof color);
for (int i = 0; i < n; i += 2) {
if (color[i])
continue;
cnt = 0;
if (!dfs(i)) {
for (int j = 0; j < cnt; j++)
color[tmp[j]] = color[tmp[j] ^ 1] = W;
if (!dfs(i ^ 1))
return false;
}
}
return true;
}
int main()
{
int u, v;
while (scanf("%d %d", &n, &m) != EOF) {
init();
while (m--) {
scanf("%d %d", &u, &v);
u--, v--;
addEdge(u, v ^ 1);
addEdge(v, u ^ 1);
}
if (solve()) {
for (int i = 0; i < n; i++)
if (color[i] == R)
printf("%d\n", i + 1);
} else
puts("NIE");
}
return 0;
}
12. POJ 3683 Priest John’s Busiest Day
13. POJ 3648 Wedding
14. POJ 3905 Perfect Election
15. HDU 4115 Eliminate the Conflict
题意:
n轮猜拳,已知对方所有出的结果,但是会给你一些限制,比如某两次你出的必须相同,或者必须不同。如果有某一局你输了或者破坏了规则,那么你就输了。
思路:
因为是猜拳,作为一个初学者,我以自身来说很容易陷入3-sat的误区,实则不然。真正决定n-sat的关键因素是 限制句子中的变量数目 ,比如这里第 u 次和第 v 次必须相同或者不同,只有两个变量就必然可以用2-sat做。
这里的限制,除了相同与不同外,对于一个已知的对方猜拳结果,那么我只有两个选择才能保证不输。
比如已知对方会出剪刀(S),那么我要么出石头(R),要么出剪刀(S)。
因此建边 ~S -> R , ~R -> S 。
额外的,从最开始就有的,如果我这局出了剪刀(S) , 那么很自然地就不会是石头(R)或者布(P)了,可以换种说法,同时出 剪刀和石头 或者 剪刀和布 是矛盾的。
建边 S -> ~R , S-> ~P
我这个思路总共要建三次边……,可能有些麻烦了。
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <iterator>
#include <map>
#include <queue>
#include <set>
#include <vector>
using namespace std;
const int maxn = 60010;
int n, m, idx, scc, vis_time, top;
int head[maxn], color[maxn], st[maxn], dfn[maxn], low[maxn];
bool instack[maxn];
struct node {
int to;
int nxt;
} edges[maxn * 20];
void init()
{
for (int i = 0; i <= n * 6; i++) {
dfn[i] = head[i] = -1;
instack[i] = false;
}
idx = scc = vis_time = top = 0;
}
void addEdge(const int& u, const int& v)
{
edges[idx].to = v;
edges[idx].nxt = head[u];
head[u] = idx++;
}
void tarjan(int u)
{
int v;
low[u] = dfn[u] = vis_time++;
instack[u] = true;
st[++top] = u;
for (int id = head[u]; ~id; id = edges[id].nxt) {
v = edges[id].to;
if (dfn[v] == -1) {
tarjan(v);
low[u] = min(low[u], low[v]);
} else if (instack[v])
low[u] = min(low[u], dfn[v]);
}
if (dfn[u] == low[u]) {
scc++;
do {
v = st[top--];
instack[v] = false;
color[v] = scc;
} while (u != v);
}
}
int main()
{
int T, u, v, k, r;
scanf("%d", &T);
for (int cas = 1; cas <= T; cas++) {
scanf("%d %d", &n, &m);
init();
int six, win;
for (int i = 0; i < n; i++) {
six = i * 6;
addEdge(six + 0, six + 3);
addEdge(six + 0, six + 5);
addEdge(six + 2, six + 1);
addEdge(six + 2, six + 5);
addEdge(six + 4, six + 1);
addEdge(six + 4, six + 3);
}
for (int i = 0; i < n; i++) {
scanf("%d", &r);
r--;
six = 6 * i;
win = (r + 1) % 3;
win <<= 1, r <<= 1;
addEdge(six + (r ^ 1), six + win);
addEdge(six + (win ^ 1), six + r);
}
for (int i = 0; i < m; i++) {
scanf("%d %d %d", &u, &v, &k);
u--, v--;
int sixu = u * 6, sixv = 6 * v;
if (k) {
addEdge(sixu + 0, sixv + 1);
addEdge(sixu + 2, sixv + 3);
addEdge(sixu + 4, sixv + 5);
addEdge(sixv + 0, sixu + 1);
addEdge(sixv + 2, sixu + 3);
addEdge(sixv + 4, sixu + 5);
} else {
addEdge(sixu + 0, sixv + 0);
addEdge(sixu + 2, sixv + 2);
addEdge(sixu + 4, sixv + 4);
addEdge(sixv + 0, sixu + 0);
addEdge(sixv + 2, sixu + 2);
addEdge(sixv + 4, sixu + 4);
}
}
for (int i = 0; i < 6 * n; i++)
if (dfn[i] == -1)
tarjan(i);
bool flag = true;
for (int i = 0; i < 3 * n; i++)
if (color[i << 1] == color[i << 1 | 1]) {
flag = false;
break;
}
printf("Case #%d: ", cas);
puts(flag ? "yes" : "no");
}
return 0;
}
16. 总结
至此,2-SAT专题基本结束了 (2017.6.2 POJ 挂了好久了……
不得不说,2-SAT的题目在这个强联通tarjan这个算法上考得很少,更多的是把重点放在逻辑性非常强的建边上,我这个菜鸡,很多题目都只能看题解感慨……修炼还是远远不够……
我这里小小总结一下我对2-SAT题目的着重点和做法。
- 找出限制语句 这是很关键的,一般的题目都会很明显地告诉你。
- 从限制中抓出一个一个矛盾。n-SAT是解决矛盾给出适应解的问题,在你的逻辑内,必须抓出每个矛盾并解决它才能保证你逻辑的严密性。
- 当然对于tarjan的理解也算比较重要,但只要以上两个处理的好,逻辑缜密,tarjan大都只是套模板而已。