POJ 3207 Ikki's Story IV - Panda's Trick(2-sat問題)
Description
liympanda, one of Ikki’s friend, likes playing games with Ikki. Today after minesweeping with Ikki and winning so many times, he is tired of such easy games and wants to play another game with Ikki.
liympanda has a magic circle and he puts it on a plane, there are n points on its boundary in circular border: 0, 1, 2, …, n − 1. Evil panda claims that he is connecting m pairs of points. To connect two points, liympanda either places the link entirely inside the circle or entirely outside the circle. Now liympanda tells Ikki no two links touch inside/outside the circle, except on the boundary. He wants Ikki to figure out whether this is possible…
Despaired at the minesweeping game just played, Ikki is totally at a loss, so he decides to write a program to help him.
Input
The input contains exactly one test case.
In the test case there will be a line consisting of of two integers: n and m (n ≤ 1,000, m ≤ 500). The following m lines each contain two integers ai and bi, which denote the endpoints of the ith wire. Every point will have at most one link.
Output
Output a line, either “panda is telling the truth...” or “the evil panda is lying again”.
Sample Input
4 2
0 1
3 2
Sample Output
panda is telling the truth...
Http
POJ:https://vjudge.net/problem/POJ-3207
Source
2-sat
題目大意
給定一個圓及其上面的n個點,現在要連線上面的m對點,可以從圓外或圓內連線,要求不能相交。現在問這樣的方案是否存在
解決思路
對於圓內和圓外我們可以把其看做兩種狀態,那麼對於每一對點,我們把從圓外連線記作i,把從圓內連線記作i+m。
那如何連線邊呢?我們知道在2-sat問題中若連線i->j則表示若取i則必取j,轉換到這一題就是要找出那些會出現矛盾的點對,即兩組點對不能同時從外面或同時從裡面連線。
為了方便操作,我們在輸入的時候就點對(x,y)x,y中較小的一個放到x中,大的放到y中,這樣我們就可以把圓化成一個線段,線上段上處理了(為什麼呢,仔細想一想)。
掃描所有點對,每次列舉點對i,j,那麼出現矛盾會是什麼情況呢?
當xj在xi與yi之間且yj不再xi與yi之間時,兩組點對不能同時在圓外或圓內。所以可以以此建圖。
因為這個題目只要判斷可行性,所以在判斷2-sat時既可以用在這一題中的dfs染色判斷法,也可以用Tarjan縮點求強聯通分量的方法。因為上一篇文章已經介紹過了染色法,加上這一題資料範圍更大,所以這裡介紹一下Tarjan法。
關於如何用Tarjan求強連通分量,這裡不再重複(如果不知道,可以到這篇文章去看一看(施工中)),那麼主要講一講為什麼Tarjan縮點後就可以判斷2-sat的可行性。
因為我們在2-sat建圖時對於一條邊i->j的意思是選i必須選j,而縮點後每一個強連通分量代表著互相可達,即表示若選擇其中的一個則整個強聯通分量中的點都必須選擇。而若此時i與i+m在同一個強連通分量裡,說明無解(一對點總不能既從圓外連,又從圓內連吧)。
程式碼
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<vector>
#include<stack>
using namespace std;
const int maxN=2001;
const int inf=2147483647;
int n,m;
int Readx[maxN];
int Ready[maxN];
vector<int> E[maxN];//i與i+m為對應點
//Tarjan
int cnt=0;
int dfn[maxN];
int low[maxN];
bool instack[maxN];
stack<int> S;
//連通塊(這裡我就用拼音寫法啦)
int LTKcnt=0;
int LTK[maxN];
void Read_and_Init();//讀入並建圖
void Link(int x,int y);//連線x與y
void tarjan(int u);
bool check();
int main()
{
Read_and_Init();
memset(instack,0,sizeof(instack));
memset(dfn,0,sizeof(dfn));
while (!S.empty())
S.pop();
for (int i=1;i<=2*m;i++)
if (dfn[i]==0)
tarjan(i);
if (check())
cout<<"panda is telling the truth..."<<endl;
else
cout<<"the evil panda is lying again"<<endl;
}
void Read_and_Init()
{
cin>>n>>m;
int a,b;
for (int i=1;i<=m;i++)
{
cin>>Readx[i]>>Ready[i];
Readx[i]++;
Ready[i]++;
if (Readx[i]>Ready[i])//為了方便後面判斷,將小的點放在前面
swap(Readx[i],Ready[i]);
}
for (int i=1;i<=m;i++)
for (int j=i+1;j<=m;j++)
{
if ( ((Readx[i]<=Readx[j])&&(Readx[j]<=Ready[i])&&(Ready[i]<=Ready[j]))
|| ((Readx[i]>=Readx[j])&&(Readx[i]<=Ready[j])&&(Ready[j]<=Ready[i])) )//這裡即表示若i與j矛盾
{
Link(i,j+m);
Link(j,i+m);
Link(i+m,j);
Link(j+m,i);
}
}
return;
}
void Link(int x,int y)
{
E[x].push_back(y);
return;
}
void tarjan(int u)//Tarjan演算法,不多說
{
cnt++;
dfn[u]=low[u]=cnt;
instack[u]=1;
S.push(u);
for (int i=0;i<E[u].size();i++)
{
int v=E[u][i];
if (dfn[v]==0)
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else
if (instack[v]==1)
low[u]=min(low[u],dfn[v]);
}
if (dfn[u]==low[u])
{
int v;
LTKcnt++;
do
{
v=S.top();
S.pop();
instack[v]=0;
LTK[v]=LTKcnt;
}
while (u!=v);
}
return;
}
bool check()//檢查是否有i與i+m在同一連通塊
{
for (int i=1;i<=m;i++)
if (LTK[i]==LTK[i+m])
return 0;
return 1;
}