poj 3207 Ikki's Story IV - Panda's Trick 2-sat+建图

5 篇文章 0 订阅
1 篇文章 0 订阅
/*
2-sat
题意:一个环上有n个点,有m对点之间有连接,连接可以在环内,也可以在环外,问这些连接是否相交
重点还是在建图,每个连接作为一个对象,他有两个状态,在环内,在环外
若两连接相交,则必是一个在环内,一个在环外,
如a、b相交,包含同在环内相交,同在环外相交,则
同在内相交:
a在内(a*2),那么b必在外(b*2+1)
b在内(b*2),那么a必在外(a*2+1)
同在外相交:
a在外(a*2+1),那么b必在内(b*2)
b在外(b*2+1),那么a必在内(a*2)
如此建边即可

*/
#include <stdio.h>
#include <string.h>
#include <stack>
using namespace std;
struct edge
{
    int ansi;
    int v[1000000];
    int next[1000000];
    int head[1000000];
    edge()
    {
        clear();
    }
    void clear()
    {
        ansi = 1;
        memset(head, 0, sizeof(head));
    }
    void add(int u, int w)
    {
        v[ansi] = w;
        next[ansi] = head[u];
        head[u] = ansi;
        ansi++;
    }
} e1;
int dfn[5000], low[5000];
int n, m, index, scc;
stack<int>ss;
int instack[5000], belong[5000];
struct link
{
    int x, y;
} l[1200];
void tarjan(int u)//tarjan求强连通分量
{
    int i, v;
    dfn[u] = low[u] = index++;
    instack[u] = 1;
    ss.push(u);//之前误写成队列
    for (i = e1.head[u]; i; i = e1.next[i])
    {
        v = e1.v[i];
        if (dfn[v] == 0)
        {
            tarjan(v);
            if (low[v] < low[u])
                low[u] = low[v];
        }
        else if (instack[v] && dfn[v] < low[u])
            low[u] = dfn[v];
    }
    if (low[u] == dfn[u])
    {
        do
        {
            v = ss.top();
            ss.pop();
            instack[v] = 0;
            belong[v] = scc; //标记所属强连通分量的标号
        }
        while (v != u);
        scc++;
    }
}
int jiao(int j, int i)
{
    if (l[j].x < l[i].x && l[i].x < l[j].y && l[j].y < l[i].y)
        return 1;
    if (l[i].x < l[j].x && l[j].x < l[i].y && l[i].y < l[j].y)
        return 1;
    return 0;
}
int main()
{
    e1.clear();
    int i, j;
    scanf("%d%d", &n, &m);
    for (i = 0; i < m; i++)
    {
        scanf("%d%d", &l[i].x, &l[i].y);
        if (l[i].x > l[i].y)
        {
            int tmp = l[i].x;
            l[i].x = l[i].y;
            l[i].y = tmp;
        }
        for (j = 0; j < i; j++)
        {
            if (jiao(j, i))
            {
                e1.add(i * 2, j * 2 + 1);
                e1.add(j * 2, i * 2 + 1);
                e1.add(i * 2 + 1, j * 2);
                e1.add(j * 2 + 1, i * 2);
            }
        }
    }


    index = 1; //dfn[]标记
    scc = 0; //强连通分量个数,从0开始计数
    memset(dfn, 0, sizeof(dfn));
    memset(instack, 0, sizeof(instack));
    for (i = 0; i < 2 * m; i++) 
    {
        if (!dfn[i])
            tarjan(i);
    }
    for (i = 0; i < m; i++)
    {
        if (belong[2 * i] == belong[2 * i + 1])
            break;
    }
    if (i == m)
        printf("panda is telling the truth...\n");
    else printf("the evil panda is lying again\n");
    return 0;
}


-------------------————————————————————————————————————————————————————————————

题意:一个圆盘上有n个点,编号顺次为0……n-1,现在给出m条线段,这些线段可以在盘的正面,也可以在反面,要求判断最终是否有线段不相交的安排,使满足要求.
      令第i条线(i从0开始)在正面为第2i个顶点,在反面为第2i+1个顶点.若第i条线和第j条线构成一个四边形的两条对角线,则在2i和2j+1之间连一条双向边,2i+1和2j之间连一条双向边.这样建图后求强连通分量,看点2i和点2i+1是否在同一个强连通分量中,如果是在同一强连通分量中,就说明不满足要求,如果没有则满足. 
      首先,我们知道若第i条线和第j条线构成一个四边形的两条对角线,则2i和2j不相容,又2i和2i+1中必须出现一个,且2j和2j+1中也必须出现一个,所以可以说2i+1和2j+1中至少出现一个,换句话说, 从2i到2j+1连一条有向边表示如果选择了2i就必须选择2j+1,从2j到2i+1连一条有向边表示如果选择了2j就必须选择2i+1,同理对于2i+1和2j+1的不相容也可以连两条有向边,所以最后跟上面的组成了两条双向边.
      可能是由于使用了STL中的vector吧,所以速度很慢.

#include<iostream>
#include<string>
#include<cstdlib>
#include<vector>
#include<algorithm>
using namespace std;
const int MAXN = 1005;
const int MAXM = 505;
int dfn[MAXN], low[MAXN], stack[MAXN], belong[MAXN], a[MAXM], b[MAXM];
int top, cnt, n, m, ans;
bool instack[MAXN];
vector<int>tree[MAXN];
void Input()
{
    int i, j;
    for (i = 0; i < 2 * m; i++)
        tree[i].clear();
    scanf("%d%d", &n, &m);
    for (i = 0; i < m; i++)
    {
        scanf("%d%d", &a[i], &b[i]);
        if (a[i] > b[i])   //节点是顺时针排列的
            swap(a[i], b[i]);
    }
    for (i = 0; i < m; i++)
        for (j = i + 1; j < m; j++)   //建图
            if ((a[i] < a[j] && b[i] < b[j] && a[j] < b[i]) || (a[i] > a[j] && b[i] > b[j] && a[i] < b[j]))  //编号为i,j的两条线段相交
            {
                tree[2 * i].push_back(2 * j + 1);
                tree[2 * i + 1].push_back(2 * j);
                tree[2 * j].push_back(2 * i + 1);
                tree[2 * j + 1].push_back(2 * i);
            }
}
void DFS(int i)
{
    int j, k;
    dfn[i] = low[i] = ++ans;
    instack[i] = true;
    stack[++top] = i;
    for (k = 0; k < tree[i].size(); k++)
    {
        j = tree[i][k];
        if (!dfn[j])
        {
            DFS(j);
            if (low[j] < low[i])
                low[i] = low[j];
        }
        else if (instack[j] && dfn[j] < low[i])
            low[i] = dfn[j];
    }
    if (dfn[i] == low[i])
    {
        cnt++;
        do
        {
            j = stack[top--];
            instack[j] = false;
            belong[j] = cnt;
        }
        while (j != i);
    }
}
void Tarjan()  //Tarjan算法求解强连通分量
{
    int i;
    top = cnt = ans = 0;
    memset(dfn, 0, sizeof(dfn));
    for (i = 0; i < 2 * m; i++)
        if (!dfn[i])
            DFS(i);
}
bool Judge()  //判断是否存在可行解
{
    int i;
    for (i = 0; i < 2 * m; i += 2)
        if (belong[i] == belong[i + 1])   //即不相容的两条边在同一强连通分量中
            return false;
    return true;
}
int main()
{
    Input();
    Tarjan();
    if (Judge())
        printf("panda is telling the truth...\n");
    else
        printf("the evil panda is lying again\n");
    system("pause");
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值