P5782 [POI2001] 和平委员会

原题传送门

题目大意

根据宪法,Byteland 民主共和国的公众和平委员会应该在国会中通过立法程序来创立。 不幸的是,由于某些党派代表之间的不和睦而使得这件事存在障碍。 此委员会必须满足下列条件:

  • 每个党派都在委员会中恰有 1 1 1 个代表。
  • 如果 2 2 2 个代表彼此厌恶,则他们不能都属于委员会。

每个党在议会中有 2 2 2 个代表。代表从 1 1 1 编号到 2 n 2n 2n。 编号为 2 i − 1 2i-1 2i1 2 i 2i 2i 的代表属于第 i i i 个党派。

任务:写一程序读入党派的数量和关系不友好的代表对,计算决定建立和平委员会是否可能,若行,则列出委员会的成员表。

如果不能创立委员会,则输出信息 NIE

解题思路

比较裸的 2-SAT 问题。

2-SAT 都是指给你许多约束条件,类似于二选一之类的,让你输出任意一种合法解。

如果是 k k k 选一,那就是 k-SAT 问题。

首先思考,如果是二选一的问题,那是不是这两个互相排斥的点不能连在一起?

解题流程:

  1. 对于所有两个互相排斥的点,就不能连边,将集合里没有明确关系的点就连一条边。

  2. 然后对每一个没有在上一遍 tarjan 遍历到的点都 tarjan 一遍。

  3. 判断是否有不可满足的解。

  4. 输出正确解。

建边:
举个例子,设有 A A A B B B 两个党派,若 A 1 A_1 A1 B 2 B_2 B2 互相厌恶,则 A 1 A_1 A1 B 2 B_2 B2 无法连边,于是只能 A 1 A_1 A1 B 1 B_1 B1 连边, A 2 A_2 A2 B 2 B_2 B2 连边。

注意点的数量要改是 2 n 2n 2n

若一个点的编号为 x x x,则与他在同一个党派的另一个代表的计算方法则为:

int js(int x)
{
	return (x % 2) ? x + 1 : x - 1;
}

然后对每一个没有遍历到的点,都跑一边 tarjan

然后看第一个条件:

每个党派都在委员会中恰有 1 1 1 个代表。

只需判断同一党派的两个代表是否在同一个强连通分量。

for(int i = 1; i <= 2 * n; ++i)
{
	if(i % 2 == 1 && id[i] == id[i + 1])
	{
		printf("NIE\n");
		return 0;
	}
}

再看第二个条件:

如果 2 2 2 个代表彼此厌恶,则他们不能都属于委员会。

再刚刚建边的时候已经讨论过了,直接跳过。

最后输出部分,:

for(int i = 1; i <= 2 * n; ++i)
{
	if(id[i] < id[js(i)])
	{
		cout << i << endl;
	}
}

Special Judge:如果委员会能以多种方法形成,程序可以只输出它们的某一个。

注意这里是小于,如果直接写等于,就会重复输出。

AC CODE

由于笔者快读太长,不方便放在这里,请读者不要复制本代码。

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

#define _ 20005

int n, m, cnt_node, cntn;
int ans[2000005];

int cnt;
array<int, 2000005> head;
struct abc
{
    int to, nxt;
};
array<abc, 2000005> dd;

array<bool, 2000005> vis;
array<int, 2000005> dfn, low, id;
stack<int> s;

inline void add(int u, int v)
{
    dd[++cnt].to = v;
    dd[cnt].nxt = head[u];
    head[u] = cnt;
}

inline void tarjan(int u)
{
    dfn[u] = low[u] = ++cnt_node;
    s.push(u);
    vis[u] = 1;
    for (int e = head[u]; e; e = dd[e].nxt)
    {
        if (!dfn[dd[e].to])
        {
            tarjan(dd[e].to);
            low[u] = min(low[dd[e].to], low[u]);
        }
        else if (vis[dd[e].to])
            low[u] = min(low[u], dfn[dd[e].to]);
    }
    if (low[u] == dfn[u])
    {
        cntn++;
        while (1)
        {
            int now = s.top();
            s.pop();
            vis[now] = 0;
            id[now] = cntn;
            if (now == u)
                break;
        }
    }
}

int js(int x)
{
	return (x % 2) ? x + 1 : x - 1;
}

signed main()
{
	cin >> n >> m;
	for(int i = 1; i <= m; ++i)
	{
		int a, b;
		cin >> a >> b;
		add(a, js(b));
		add(b, js(a));
	}
	for(int i = 1; i <= 2 * n; ++i)
		if(!dfn[i]) tarjan(i);
	for(int i = 1; i <= 2 * n; ++i)
	{
		if(i % 2 == 1 && id[i] == id[i + 1])
		{
			printf("NIE\n");
			return 0;
		}
	}
	for(int i = 1; i <= 2 * n; ++i)
	{
		if(id[i] < id[js(i)])
		{
			cout << i << endl;
		}
	}
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值