P5782 和平委员会 题解

P5782 和平委员会 - 传送门

题目分析

2 - SAT 板子题是这样的:

给 n 个节点, m 个类似 ¬ a ∨ b \lnot a \lor b ¬ab (满足 a ← f a l s e a \gets false afalse 或者 b ← t r u e b \gets true btrue)的条件,求是否能给 n 个节点赋值,使其满足那 m 个条件。

此题概述:

此题与模板题的不同在于:

  1. 一党派的 2 名代表的编号是连续的;

  2. 相互排斥的 2 名代表不能同时存在于委员会(建图)。

求解方法:

(若您对 2 - SAT 没有学习、接触过,请您先做做 P4782 2 - SAT 模板 。)

  1. 编号连续:

    当一党派中的两个代表标号连续时,我们想通过第 i 名代表的编号找到另一名代表的编号就不像直接找 i + n i+n i+n 那么简单了。

    啊这个也简单。 我们写一个函数:

    判断当前编号 x 奇偶性,若 x 为奇数,另一名的编号为 x+1 ;反之,另一名的编号为 x-1 。

  2. 建图:

    假设我们现在已知两个党派,其党员分别为 A 、B 以及 C 、D。且已知 A 与 C 有仇,即他们不能同时在委员会中。

    那么此时我们将怎么建边呢?

    我们建两条有向边:

    • 若 C 在委员会中,那么 A 绝对不能再委员会中,又因为题目要求,所以必须让 B 再委员会中;

    • 若 A 在委员会中,则只能让 D 在委员会中(理论同上)。

    这样的建边原理和模板题一样,但此题的建边明显比模板要简单。

  3. 判断、输出答案:

    这点简单,需要注意的是,我们在循环时,只能循环编号为奇数的点或者编号为偶数的点,避免重复(同时这样处理也是较为简单的方式)。


综上,这是这道题的解析 (我废话有点多…)

代码奉上:

#include<bits/stdc++.h>
using namespace std;

const int maxn = 2000005;
int n, m;
int cnt, hd[maxn];
struct node{
	int to, nxt;
}e[maxn * 2];
int dfn[maxn], low[maxn], co[maxn];
int tmp, top, col;
int st[maxn];

void add (int u, int v)
{
	e[++cnt].nxt = hd[u];
	e[cnt].to = v;
	hd[u] = cnt;
}

int find (int x)
{
	if (x % 2 == 1) return x + 1;
	return x - 1; 
}

void tarjan (int u)
{
	dfn[u] = low[u] = ++tmp;
	st[++top] = u;
	for (int i = hd[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (!dfn[v])
		{
			tarjan (v);
			low[u] = min (low[u], low[v]);
		}
		else if (!co[v]) low[u] = min (low[u], dfn[v]);
	}
	if (dfn[u] == low[u])
	{
		co[u] = ++col;
		while (st[top] != u)
		{
			co[st[top]] = col;
			top--;
		}
		top--;
	}
}

int main ()
{
	scanf ("%d %d", &n, &m);
	for (int i = 1; i <= m; i++)
	{
		int a, b;
		scanf ("%d %d", &a, &b);
		add (a, find (b));
		add (b, find (a));
	}
	for (int i = 1; i <= n * 2; i++)
	{
		if (!dfn[i]) tarjan (i);
	}
	for (int i = 1; i <= n; i += 2)
	{
		if (co[i] == co[i + 1])
		{
			printf ("NIE\n");
			return 0;
		}
	}
	for (int i = 1; i <= 2 * n; i += 2)
	{
		if (co[i] <= co[i + 1]) printf ("%d\n", i);
		else printf ("%d\n", i + 1);
	}
	printf ("\n");
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值