您的位置:首页 > 其它

POJ 3207 Ikki's Story IV - Panda's Trick 强连通分量或并查集+2sat

2016-10-12 20:13 246 查看
标签:解题报告 图论

原题见POJ 3207

有n个点围成一个圈,给出m条可以弯曲的边,使得点两两相连。这些边可以在圆内画,也可以在圆外画。问是否有可以让边不相交的画法。

分析

每条边都有两种选择,画在内部或者外部。以这两种状态来标记边,共有m对状态,对第i条边,以
2i
,
2i+1
来标记两种状态。而且这种状态必须选一种作为结果。这符合2-sat的说法。

判断是否有两条边i,j会相交。若相交,则在图中添加四条有向边:
2j+1<->2i
,
2i+1<->2j
,形成有向图。对于有向图中的
u->v
表示选了u必须选择v。

对强连通分量进行缩点,表示如果
2i
,
2i+1
在同一强连通分量中,则无解。否则有解。

怀疑太简单?缩完点的有向无环图一定有解吗?

假设无解,那必然是因为
2i
,
2i+1
在一个连通图中。不妨设存在一个子图a,有
2i->a
a->2i+1
,则必存在边
2i<->a
a<->2i+1
,形成有环图,矛盾。

Tarjon+2sat版本代码

/*--------------------------------------------
* File Name: POJ 3207
* Author: Danliwoo
* Mail: Danliwoo@outlook.com
* Created Time: 2016-10-12 16:13:50
--------------------------------------------*/

#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <cmath>
using namespace std;
#define N 510
int n, m;
struct edge
{
int u, v;
void sc() {
scanf("%d%d", &u, &v);
}
}p
;

std::vector<int> G[N<<1];
int out[N<<1];
void add(int x, int y) {
G[x].push_back(y);
out[x]++;
}
int dt(int x) {
return x == 0 ? 0 : (x > 0 ? 1 : -1);
}
bool cross(edge a, edge b) {
return dt((a.u - b.u) * (a.v - b.u))
* dt((a.u - b.v) * (a.v - b.v)) < 0;
}
void make() {
memset(G, 0, sizeof(G));
memset(out, 0, sizeof(out));
for(int i = 0;i < m;i++)
for(int j = 0;j < i;j++) {
if(cross(p[i], p[j])) {
add(i<<1, j<<1|1);
add(j<<1, i<<1|1);
add(i<<1|1, j<<1);
add(j<<1|1, i<<1);
}
}
}

int vis[N<<1], dfn[N<<1], low[N<<1], stk[N<<1], ins[N<<1], clus[N<<1];
int top, clk, cn;
void Tarjon(int x) {
dfn[x] = low[x] = clk++;
stk[top++] = x;
vis[x] = ins[x] = 1;
for(int j = 0;j < G[x].size();j++) {
int y = G[x][j];
if(!vis[y]) {
Tarjon(y);
low[x] = min(low[x], low[y]);
} else if(ins[y]) {
low[x] = min(low[x], dfn[y]);
}
}
if(low[x] == dfn[x]) {
do {
ins[stk[top-1]] = 0;
clus[stk[top-1]] = cn;
top--;
} while(stk[top] != x);
cn++;
}
}

bool solve() {
for(int i = 0;i < m;i++)
if(clus[i<<1] == clus[i<<1|1])
return false;
return true;
}
int main() {
while(~scanf("%d%d", &n, &m)) {
for(int i = 0;i < m;i++)
p[i].sc();
make();
memset(vis, 0, sizeof(vis));
cn = top = clk = 0;
for(int i = 0;i < (m<<1);i++)
if(!vis[i]) Tarjon(i);
if(solve()) printf("panda is telling the truth...\n");
else printf("the evil panda is lying again\n");
}
return 0;
}


并查集+2sat版本

实际上,这题由于双向对称的缘故,连的都是无向边,经过缩点后即形成多个点,点与点之间若有边,则两点又被合并为一个点。用并查集也能搞缩点。

/*--------------------------------------------
* File Name: POJ 3207
* Author: Danliwoo
* Mail: Danliwoo@outlook.com
* Created Time: 2016-10-12 17:05:44
--------------------------------------------*/

#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <cmath>
using namespace std;
#define N 510
int n, m;
struct edge
{
int u, v;
void sc() {
scanf("%d%d", &u, &v);
}
}ed
;
int p[N << 1];
int get(int x) {
if(x != p[x]) p[x] = get(p[x]);
return p[x];
}
void un(int x, int y) {
get(x); get(y);
if(p[x] != p[y]) p[p[x]] = p[y];
}
int dt(int x) {
return x == 0 ? 0 : (x > 0 ? 1 : -1);
}
bool cross(edge a, edge b) {
return dt((a.u - b.u) * (a.v - b.u))
* dt((a.u - b.v) * (a.v - b.v)) < 0;
}
void make() {
for(int i = 0;i < 2*m;i++)
p[i] = i;
for(int i = 0;i < m;i++)
for(int j = 0;j < i;j++) {
if(cross(ed[i], ed[j])) {
un(i<<1, (j<<1|1));
un(j<<1, (i<<1|1));
}
}
}
bool solve() {
for(int i = 0;i < m;i++)
if(p[i<<1] == p[i<<1|1])
return false;
return true;
}
int main() {
while(~scanf("%d%d", &n, &m)) {
for(int i = 0;i < m;i++)
ed[i].sc();
make();
if(solve()) printf("panda is telling the truth...\n");
else printf("the evil panda is lying again\n");
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: