poj3207 Ikki's Story IV - Panda's Trick(2 SAT)

本文探讨了一个经典的2-SAT问题实例——在平面上的圆周上放置若干点,并尝试通过圆内外连线的方式使得任意两条连线不相交。文章详细介绍了如何将此问题转化为2-SAT问题并通过Tarjan算法进行求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接

题意:
平面上有一个圆,圆的边上按顺时针放着n个点
现在要连m条边(a,b),a到b可以从圆的内部连接,也可以从圆的外部连接
信息中,每个点最多只会连接的一条边,问能不能连接这m条边,使这些边都不相交

分析:
经典的2_SAT问题

建图:对于一条边i,在圆内记为i,在圆外记为i’。

设边 i i 连接点A,B,边 j j 连接点C,D
i i j在圆内是否相交就是线段 AB A B 与线段 CD C D 是否相交
可以证明,如果 i i j在圆内不能共存,则在圆外也一定不能共存,即:
i i 在圆内,则j一定在圆外,建边 i>j i − > j ′
i i 在圆外,则j一定在圆内,建边 i>j i ′ − > j
j j 在圆内,则i一定在圆外,建边 j>i j − > i ′
j j 在圆外,则i一定在圆内,建边 j>i j ′ − > i

因为只是判定性问题,所以一遍tarjan缩点判断冲突的点是不是在一个scc中即可

摘录一点dada的言论,方便理解:

不要急,请先达成一点共识:
1. 如果很多节点已经处在一个强连通分量内了,也就是它们两两均可互达了,那么意味着选择了其中的一个,整个强连通分量都将被选择,如果不选择其中的一个,那么整个强连通分量内都不能够被选择
2. 如果一个集合的两个元素处在了同一个强连通分量内,也就是说要么都不选,要么都被选,那么题目要求的选出一个就一定无法成立了
3. 题中不管给出哪两个不能同时选,都不能改变我们连的边都是对称的这一事实,只要A到B有边,那么B的对立节点到A的对立节点就有边
4. 边的对称意味着图的对称,原图的对称意味着哪怕用强连通分量缩点后的图一样对称,所以对立节点的概念也可以应用在强连通分量上
5. 为了叙述方便,以下简称强连通分量为块,注意:真正的块指的是点双连通分量,这里只是一种形象化的表述,是不规则的!

tip

2_SAT第一题,就当是临阵磨枪,临时抱佛脚吧

把空间算好了

#include<cstdio>
#include<cstring>
#include<iostream>

using namespace std;

const int N=2010;
struct node{
    int x,y,nxt;
};
node way[500010];
int st[N],tot=0,n,m,x[N],y[N];

int jiao(int a,int b) {
    if (x[a]<=x[b]&&x[b]<=y[a]&&y[b]>=y[a]) return 1;
    if (x[b]<=x[a]&&x[a]<=y[b]&&y[a]>=y[b]) return 1;
    return 0;
}

void add(int u,int w) {
    tot++;way[tot].x=u;way[tot].y=w;way[tot].nxt=st[u];st[u]=tot;
}

int dfn[N],low[N],clo=0,cnt=0,belong[N],S[N],top=0;
bool vis[N];

void tarjan(int now) {
    S[++top]=now;
    dfn[now]=low[now]=++clo;
    vis[now]=1;
    for (int i=st[now];i;i=way[i].nxt)
        if (!dfn[way[i].y]) {
            tarjan(way[i].y);
            low[now]=min(low[now],low[way[i].y]);
        }
        else if (vis[way[i].y]) 
            low[now]=min(low[now],dfn[way[i].y]);
    if (low[now]==dfn[now]) {
        cnt++;
        int x=0;
        while (x!=now) {
            x=S[top--];
            vis[x]=0;
            belong[x]=cnt;
        }
    }
}

bool solve() {
    for (int i=1;i<=m;i++)
        if (!dfn[i])
            tarjan(i);
    for (int i=1;i<=m;i++)
        if (belong[i]==belong[i+m]) return 0;
    return 1;
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++) {
        scanf("%d%d",&x[i],&y[i]);
        if (x[i]>y[i]) swap(x[i],y[i]);
    }
    for (int i=1;i<=m;i++)
        for (int j=i+1;j<=m;j++) 
            if (jiao(i,j)) {
                add(i,j+m); add(j,i+m);
                add(j+m,i); add(i+m,j);
            } 
    if (solve()) printf("panda is telling the truth...\n");
    else printf("the evil panda is lying again\n");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值