set+并查集的碰撞:HDU1272
首先把一篇很好的set总结贴在这里
然后把题目链接放过来
Problem Description
上次Gardon的迷宫城堡小希玩了很久(见Problem B),现在她也想设计一个迷宫让Gardon来走。但是她设计迷宫的思路不一样,首先她认为所有的通道都应该是双向连通的,就是说如果有一个通道连通了房间A和B,那么既可以通过它从房间A走到房间B,也可以通过它从房间B走到房间A,为了提高难度,小希希望任意两个房间有且仅有一条路径可以相通(除非走了回头路)。小希现在把她的设计图给你,让你帮忙判断她的设计图是否符合她的设计思路。比如下面的例子,前两个是符合条件的,但是最后一个却有两种方法从5到达8。
Input
输入包含多组数据,每组数据是一个以0 0结尾的整数对列表,表示了一条通道连接的两个房间的编号。房间的编号至少为1,且不超过100000。每两组数据之间有一个空行。
整个文件以两个-1结尾。
Output
对于输入的每一组数据,输出仅包括一行。如果该迷宫符合小希的思路,那么输出"Yes",否则输出"No"。
Sample Input
6 8 5 3 5 2 6 4
5 6 0 08 1 7 3 6 2 8 9 7 5
7 4 7 8 7 6 0 03 8 6 8 6 4
5 3 5 6 5 2 0 0-1 -1
Sample Output
Yes
Yes
No
首先记住一个规律:没有自环而且把所有点都连起来的情况下,点数等于边数+1。可以这样理解,两个点之间只有一条路的话就只连一个线,然后突然来了第三个点,那么他如果和两个点都连上,就是一个三角形、不符合不成环、所以只能和一个点连起来,整个系统增加了一个点和一条边,条减边依旧是1、那么同样,三个点有两个边,再来一个点,也只能和一个点连起来。。。。。。这样不管有多少点,只要满足不成环而且所有点都连起来,那么势必就是点=边+1
如图:
图1和图2都满足上面的情况,第一个图一共6个点,5条边,可以发现没办法在满足点数量不变而且不成环的情况下再加一条边了;
而图三在3和8之间连了一条线,显然5 3 8 6四个点形成了一个环,题意是不会让你成环的(如何判断成环技能get)
但是有个问题
没有自环而且把所有点都连起来的情况下是点数=边数+1的充分条件,反过来说就不一定成立
如图
这里是四个点三条边,但是由于有两个点之间有两条线链接、于是四个圆分成了2个不相关的区域,没有一条线连接他们
所以既然是学习并查集吗,就好好体会一下并查集是干什么的,功能是什么把
储存点用set最适合不过,因为他的元素是不重复的,输入即表示了点,由表示了两点之间的连接,不用set的情况下就要开一个jud数组去判断一个点是否是新来的,然后统计点的数量就很麻烦
而set你插入旧点进去size也不会加
并查集板子:
//祖宗就是数组值等于自身下标的计数器
void intital_set(int x) {//对数组初始化成值和下标一致,
//最开始大家都是祖宗
for (int i = 1; i <= x; i++)
M[i] = i;
}
int find(int a) {
return M[a] == a ? a : M[a] = find(M[a]);//顺手路径压缩,
//这样如果a的祖先也不是真正的祖先直接连接到祖先的祖先
}
void unionset(int a,int b) {
M[a] = find(a);
M[b] = find(b);
if (M[a] == M[b]) {//这道题里祖先相等说明成环了,
//板子里来说这一步直接return
//(位于一个能联通的集合内还要连接不就是成环了吗)
flag_circle = true;//标记上,说明成环,放到下面判断
}
else {
M[M[a]] = M[b];//祖先弄成一样的,
//说明他们在一个联通集里面
}
}
能联通在这题的体现就是两个点的祖先一样,也就是说,如果两个点的祖先一致,那么一定有一条路(不在乎有多曲折)能够从一方走到另一方,说是祖先其实感觉不太准确,毕竟祖先去当儿子(咳咳。。。。),但是却可以体现"点连在一起"的性质(至于单向边怎么表示还不会),应用起来就是方便快速的理解如何判断两个点是否有双向边
AC代码(吐槽下直接输入0 0的情况,好坑啊,题目也没说a)
#include<iostream>
using namespace std;
typedef long long ll;
inline ll read() {
ll c = getchar(), Nig = 1, x = 0;
while (!isdigit(c) && c != '-')c = getchar();
if (c == '-')Nig = -1, c = getchar();
while (isdigit(c))x = ((x << 1) + (x << 3)) + (c ^ '0'), c = getchar();
return Nig * x;
}
inline void Out(ll a)
{
if (a < 0)putchar('-'), a = -a;
if (a >= 10)Out(a / 10);
putchar(a % 10 + '0');
}
int f[1007];
void intital_set(int x) {
for (int i = 1; i <= x; i++)
f[i] = i;//初始化数组
}
int getfather(int x) {
if (f[x] != x) {
f[x] = getfather(f[x]);
}
return f[x];//返回x的话相当于没压缩路径,达不到儿子直接连祖宗的效果,当然,也只是暂时的连着祖先,如果祖先后续当儿子,另说
}
void unionset(int x, int y) {
f[x] = getfather(x);//找一下祖先,不写的话找的只是父亲不是祖先,最后变成比较父亲一不一样
f[y] = getfather(y);//不能保证之前的祖先f【x】不变成别人的儿子。。。总感觉这段话怪怪的
if (f[x] == f[y])//祖先一样那没事了,一个家族的
return;
f[f[x]] = f[y];//x家族做y家族儿子//操作对象应该是祖先,两边不加多套f操作的是孩子的f值,会导致儿子换祖先,而且换的y还不是y家族的祖先
}
int main() {
int num_point;//点数
int num_road;//路径数
int x, y;
int time = 0;
intital_set(1000);
while (num_point = read()) {
if (!num_point) {
break;
}
num_road = read();
for (int i = 1; i <= num_road; i++) {
x = read();
y = read();
unionset(x, y);
}
for (int i = 1; i <= num_point; i++) {
if (f[i] == i) {//是祖先
time++;
}
}
Out(--time);//祖先数量-1等于完全联通所需的路程,即把祖先之间在连起来就行了
puts("");
intital_set(num_point);//数组状态返回
time = 0;//置零
}
}