[poj 3207] Ikki\'s Story IV - Panda\'s Trick(2-sat or 并查集)

这是A掉的第一道2-sat题目。由于题目的特殊性,可以不需要用tarjan计算强连通图,用并查集也可以轻松搞定。
题意:一个圆上依次分布着一些点,距离不重要。然后给你一些点对,要你将这两个点连线,问存不存在一种方案,使得在所有线段都不交叉的情况下,能连完所有线段。显然线段不是在圆内连线就是在圆外连线。
样例数据如下:
4 2
0 1
3 2
4个点两条线,满足条件的方案有好2种,如下图,所以输出"panda is telling the truth..."

 
每个点有两种状态,内连或外连,所以只要建图以后判断同一个点是否既必须内连又必须外连即可。

 
容易看出两条线段何时一定会交叉。

 
 假设第一条线段是a-b,那么内部相交就是c-d以这样的形式在两边,也就是说c-d必须在圆外相连。
所以冲突的条件就是

bool conflict(int a, int b, int c, int d) { if(c < a && d > a && d < b) // c<a与d>a好写,千万不要遗漏d<b的条件 return true; if(c > a && c < b && d > b) return true; return false; }

找到冲突边以后建图,我用的是邻接表,要注意点对是2i,2i+1,2j,2j+1。

struct point { vector<int> v; }pt[maxn*2];

for(int i = 0; i < n - 1; i++) {

for(int j = i+1; j < n; j++) { if(conflict(tt[i].s, tt[i].t, tt[j].s, tt[j].t)) { pt[i*2].v.push_back(j*2+1); pt[j*2+1].v.push_back(i*2); pt[i*2+1].v.push_back(j*2); pt[j*2].v.push_back(i*2+1); } } }

建图并tarjan以后,只要判断同一个点是否又得内连又得外连(无解)即可。

for(int i = 0; i < n; i++) { if(belong[i*2] == belong[i*2+1]) { flag = 1; break; } }


完整代码如下:

#include<iostream> #include<cstdio> #include<vector> using namespace std; #define maxn 1010 int n, m; struct point { vector<int> v; }pt[maxn*2]; struct node { int s; int t; }tt[maxn]; int index, stacktop, cnt; int dfn[maxn*2], low[maxn*2]; int instack[maxn*2], stap[maxn*2]; int belong[maxn*2]; int min(int a, int b) { return a < b ? a : b; } bool conflict(int a, int b, int c, int d) { if(c < a && d > a && d < b) return true; if(c > a && c < b && d > b) return true; return false; } void Clear() { index = stacktop = cnt = 0; memset(dfn, 0, sizeof(dfn)); memset(low, 0, sizeof(low)); memset(instack, false, sizeof(instack)); memset(belong, 0, sizeof(belong)); } void tarjan(int u) { dfn[u] = low[u] = ++index; instack[u] = true; stap[++stacktop] = u; for(int i = 0; i < pt[u].v.size(); i++) { if(!dfn[i]) { tarjan(i); low[u] = min(low[u], low[i]); } else if(instack[i]) { low[u] = min(low[u], dfn[i]); } } if(low[u] == dfn[u]) { cnt++; int v; do { v = stap[stacktop--]; instack[v] = false; belong[v] = cnt; }while(v != u); } } int main() { while(~scanf("%d%d", &n, &m)) { for(int i = 0; i < m; i++) { scanf("%d%d", &tt[i].s, &tt[i].t); } for(int i = 0; i < n - 1; i++) { for(int j = i+1; j < n; j++) { if(conflict(tt[i].s, tt[i].t, tt[j].s, tt[j].t)) { pt[i*2].v.push_back(j*2+1); pt[j*2+1].v.push_back(i*2); pt[i*2+1].v.push_back(j*2); pt[j*2].v.push_back(i*2+1); } } } Clear(); for(int i = 0; i < 2*n; i++) { if(!dfn[i]) tarjan(i); } int flag = 0; for(int i = 0; i < n; i++) { if(belong[i*2] == belong[i*2+1]) { flag = 1; break; } } if(flag) printf("the evil panda is lying again\n"); else printf("panda is telling the truth...\n"); } return 0; }

另外由于这道题比较特别,矛盾边为i j和i' j'

 其实就相当于无向图,这样的话图都不用建了,直接合并i与j'、i'与j到同一集合中。最后判断i和i'是否在同一集合中(无解)即可。
#include<iostream>
using namespace std;

struct node
{
	int s;
	int t;
}line[600];

int father[2010];

bool judge(int a, int b, int c, int d)
{
	if(c < b && c > a && d > b)
		return true;
	if(c < a && d > a && d < b)
		return true;
	return false;
}

int find(int x)
{
	if(x == father[x])
		return x;
	return father[x] = find(father[x]);
}

int main()
{
	int n, m;
	int x, y;
	while(~scanf("%d%d", &n, &m))
	{
		for(int i = 0; i < m; i++)
		{
			scanf("%d%d", &line[i].s, &line[i].t);
		}
		for(int i = 0; i < n; i++)
			father[i] = i;
		for(int i = 0; i < m-1; i++)
		{
			for(int j = i+1; j < m; j++)
			{
				if(judge(line[i].s, line[i].t, line[j].s, line[j].t))
				{
					x = find(2*i);
					y = find(2*j+1);
					father[x] = y;
					
					x = find(2*i+1);
					y = find(2*j);
					father[x] = y;
				}
			}
		}
		int flag = 0;
		for(int i = 0; i < m; i++)
		{
			if(find(2*i) == find(2*i+1))
			{
				flag = 1;
				break;
			}
		}
		if(flag)
			printf("the evil panda is lying again\n");
		else
			printf("panda is telling the truth...\n");
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值