【代码超详解】CometOJ C0397 [NOI2015Day1-A]程序自动分析(离散化 + 并查集,约 430 ms)

一、题目描述

二、算法分析说明与代码编写指导

变量连等可以存储成并查集的形式。并查集将每个元素视为一个集合,然后按需合并,支持查询父节点和根节点。每个并查集有一个代表元素,查询任意两个元素是否属于一个集合可以通过比较其根(代表元素)是否相同来实现。合并两个并查集只需要将一个集合的根指向另一个的根。
题目给的约束条件只有两种,分别是形如 xi == xj 和 xi != xj 。可以发现,xi == xj 类约束条件无论数量有多少,一定是可以被满足的。最极端的情况就是 x1 == x2 == x3 == x4 ==……,这时候将全部出现的变量都赋一个相同的值就可以了。而引入 xi != xj 类的约束后,就可能与连等中的某一节冲突,从而导致不能同时满足全部的约束条件。
每个条件有两个变量,不同的变量最多有 200 000 个。但是变量的下标最大可达 1 000 000 000 ,直接开一个这么大的用于存放并查集的数组肯定会导致 MLE ,所以需要先进行离散化。
设 c 表示题目给出的 n 个条件,d 存储条件中出现的变量下标并用于离散化,r 存储并查集的根节点(代表元)。
首先在输入的时候把出现的变量都记录到数组 d 中。然后,先对 d 升序排序,再通过 algorithm 头文件中的 unique 函数(命名空间 std)将重复出现的元素都调到数组靠后的位置。注意使用 unique 函数之前需要先排好序,否则会导致错误的结果。然后将所给条件中的变量重新命名(分配新下标)。
离散化示例
通过 algorithm 中的 lower_bound 函数获得有序容器内指定值第一次出现的位置。与数组头指针相减,就可以为元素重新分配下标。离散化过程就完成了。m 代表新分配的变量数量,unique 和 lower_bound 配合能够去除重复的条件。
接下来,将 e = 1 的条件排在前方,先按照本块开头的第一段的算法概述来满足出现 = 号的条件。
开始划分并查集。一开始将出现的变量都划分成只含一个元素的集合。这个集合的根节点(代表元素)就是变量本身。本例中,将全部的变量都简记为其下标。
然后遍历全部的条件,对 e = 1 的部分,将两个变量所在的集合合并。合并需要查找其根节点,然后合并根。root 函数查找元素 x 的根节点并压缩路径,进行压缩路径可以减少查找根节点的时间,但是会失去并查集的层次记录。

(图片来源:https://www.cnblogs.com/cyjb/p/UnionFindSets.html,图片有改动)
非递归版本和递归版本的 root 函数(求根 + 压缩路径)在这里都给出来。经过反复测试,本题中递归和非递归函数求根对总体的运行时间的影响没有什么区别,可见递归的层数不多。递归函数编写速度快,但是调用函数是需要时间和额外的内存空间的,递归的层数过多会导致 Runtime Error 。inline 修饰符对包含递归的函数无效,也就是说无法通过内联来去除调用的开销。
找到根节点后,合并两个并查集。
对 e = 0 的条件,如果不等号两边的两个变量有相同的根,意味着它们出现在同一个连等式中,显然产生了矛盾,条件不可满足。此时退出循环并输出:

NO

若未产生矛盾,输出

YES

注意题目包含了 t 组数据。

三、AC 代码

注:CometOJ 的代码运行计时波动较大,耗时仅供参考。
(约 430 ms)

#include<cstdio>
#include<algorithm>
#pragma warning(disable:4996)
using namespace std;
struct condition { unsigned x, y, e; };
unsigned t, m, n, p, d[200000], r[200000], r1, r2; condition c[100000]; bool flag;
inline bool cmp(const condition& c, const condition& d) { return c.e > d.e; }
//inline unsigned root(const unsigned& x) {
//	if (x == r[x])return x;
//	return r[x] = root(r[x]);
//}
inline unsigned root(const unsigned& x) {
	unsigned y = x;
	while (r[y] != y) { y = r[y]; }
	r[x] = y; return y;
}
int main() {
	scanf("%u", &t); ++t;
	while (--t) {
		scanf("%u", &n); p = 0xffffffff, flag = 1;
		for (unsigned i = 0; i < n; ++i) {
			scanf("%u%u%u", &c[i].x, &c[i].y, &c[i].e);
			d[++p] = c[i].x, d[++p] = c[i].y;
		}
		sort(d, d + p), m = unique(d, d + p) - d;
		for (unsigned i = 0; i < n; ++i) {
			c[i].x = lower_bound(d, d + m, c[i].x) - d;
			c[i].y = lower_bound(d, d + m, c[i].y) - d;
		}
		sort(c, c + n, cmp);
		for (unsigned i = 0; i < m; ++i)r[i] = i;
		for (unsigned i = 0; i < n; ++i) {
			r1 = root(c[i].x), r2 = root(c[i].y);
			if (c[i].e)r[r2] = r1;
			else if (r1 == r2) { flag = 0; break; }
		}
		switch (flag) {
		case 1:puts("YES"); continue;
		case 0:puts("NO");
		}
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值