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

标签:解题报告 图论


原题见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->aa->2i+1,则必存在边2i<->aa<->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[N];

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[N];
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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值