题意:动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B, B吃C,C吃A。
现有N个动物,以1-N编号。每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。
有人用两种说法对这N个动物所构成的食物链关系进行描述:
第一种说法是"1 X Y",表示X和Y是同类。
第二种说法是"2 X Y",表示X吃Y。
此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
1) 当前的话与前面的某些真的话冲突,就是假话;
2) 当前的话中X或Y比N大,就是假话;
3) 当前的话表示X吃X,就是假话。
你的任务是根据给定的N(1 <= N <= 50,000)和K句话(0 <= K <= 100,000),输出假话的总数。
链接:点我
并查集拓展应用
这篇文章原创的,见笑。。。
并查集是一种优秀的数据结构,能够支持快速的查找某元素所在的集合以及合并操作。最常见的并查集的实现方式是森林,通过记录所有结点或者部分结点(下面会看到)的父节点来记录森林。并查集的详细实现方式可以参考CLRS第二十一章。
信息学竞赛中基本的并查集题目比较少,大多要对并查集进行一些拓展,多记录一些信息以完成计算。竞赛的题目有很大一部分是询问两个结点的相对信息,结点a相对结点b的信息用f(a,b)表示,这里f(a,b)就是边(a,b)的权,那么“相对”的含义就是 对任意一个结点c,有形如f(a,b)=(f(a,c)+f(c,b))%n的关系。解决这类问题的方法,就是给表示并查集的森林的边带上权,表示两个结 点之间的f值,即使用带权的有根树实现并查集,本文将其简称为带权并查集。处理这类问题的另外一个重点是,并查集中一个集合保存的是已知可以推断其f值的 结点。一个集合中各个点的相对信息能够通过对树根的相对信息计算出来,使得通过路径压缩提高速度成为可能。
下面看一个例题,如果要提交源代码请访问[1]。
食物链(来源:NOI2001)
动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B,B吃C,C吃A。现有N个动物,以1-N编号。每个动物都是A,B,C中 的一种,但是我们并不知道它到底是哪一种。有人用两种说法对这N个动物所构成的食物链关系进行描述:第一种说法是"1XY",表示X和Y是同类。第二种说 法是"2XY",表示X吃Y。此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。当一句话满足下列三条之一时, 这句话就是假话,否则就是真话。
1)当前的话与前面的某些真的话冲突,就是假话;
2)当前的话中X或Y比N大,就是假话;
3)当前的话表示X吃X,就是假话。
你的任务是根据给定的N(1<=N<=50,000)和K句话(0<=K<=100,000)输出假话的总数。
输入:第一行是两个整数N和K,以一个空格分隔。以下K行每行是三个正整数D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。若D=1,则表示X和Y是同类。若D=2,则表示X吃Y。
输出:只有一个整数,表示假话的数目。
分析:这一题涉及查找两种动物是否属于同一个集合(表示它们是同类),如果知道了两种动物是同类而原先没有记录,又要将他们合并成同一个集合,于是很自然 的想到使用并查集。这的确是一种解法,怎么根据相互吃的关系推断出哪些动物属于同类是难点。这个解法与本文无关,故不做讨论。我们仍然要使用并查集解决这 个问题,但是合并的不是同类的动物,而是已知或者可以推断出狩猎关系的动物,或者说,是有向图的强连通分支。我们用偏移量offset(a,b)表示a和 b的关系,offset(a,b)=0表示a和b是同类,offset(a,b)=1表示a吃b,offset(a,b)=2表示b吃a。那么对任意种类 东西c,显然offset(a,b)=(offset(a,c)+offset(c,b))%3。我们看到了上文提到的关系。这个关系式表明,a和b的关 系可以通过a和根的关系以及b和根的关系推断出来。
设parent[i]表示i在带权并查集中的父结点,offset[i]表示i对parent[i]的偏移量。这里路径压缩的操作除了要把 parent[i]直接置为集合的根,还要把更新offset[i]使其成为对根的偏移量。2)和3)是很容易判断的,关键是判断1)是否成立。加入对x 和y,1)不成立,那么我们要做出这样的判断,必定是因为根据已有的信息,x和y的关系(偏移量)可以找出来或者推断出来,所以x和y必定是属于同一个集 合的。因此,每当输入一组D,x,y,首先计算x和y的偏移量offset(x,y)=D-1,寻找x和y所在的集合,同时分别累加x和y到其各自的根的 偏移量,用t1和t2表示。具体地说,设x到其根的路径为x,x1,x2,...x(n),那么t1表示 offset[x]+offset[x1]+offset[x2]+...+offset[x(n-1)]。同理如果y到根路径是 y,y1,y2,...,y(m),那么t2表示offset[y]+offset[y1]+offset[y2]+...+offset[y(m- 1)]。如果x(n)=y(m),那么x和y属于同一个集合。现在已知两条从x到x(n)的路径,一条是x,x1,x2,...x(n),总的偏移量是 t1,所以x相对x(n)的偏移量是t1%3。另外一条是x,y,y1,...,y(m),总的偏移量是t+t2,所以x相对y(m)(也就是x(n)) 的偏移量是(t+t2)%3。如果t1%3!=(t+t2)%3,显然x和y的偏移量不可能为t,新输入的话就是错的。
如果x和y不属于同一个集合,根据已有信息无法判断新输入的话是不是正确的,那么就认为它是对的。由于x和y的关系已经知道,那么x所属于的集合和y所属 于的集合的成员之间的关系就都可以推断出来,因此应该把两个集合合并。为清晰起见,这里不考虑按秩合并,简单地把x所在的集合的根接到y所在的集合的跟 上,即使得parent[x(n)]=y(m),offset[x(n)]的计算也比较简单,有offset[x(n)]=(t2-t1+t)%3,注意 可能出现负数,这时候让offset[x(n)]+=3即可。这时候x和y的跟就都是y(m)了,y到根的路径还没有变,x到根的路径多出一截 (x(n),y(m))。为了让t1仍然是x到根的相对偏移量总和,让t1+=offset[x(n)]即可。
接下来的工作就是路径压缩。首先处理x,offset[x]是x到根(这时候已经变成y(m))的偏移量,所以使得offset[x]=t1%3即可,再 把parent[x]从x1改成y(m);再处理x1,显然x1到根的偏移量等于t1减去原来x到x1的偏移量,即更新前的offset[x]的值,设为 a。令t1-=a,offset[x1]=t1%3,parent[x1]=y(m)即可;其余结点x2,...,x(n)同样方法处理。再对y进行路径 压缩即可。
小结一下,带权并查集所合并的并不一定是等价类(具体定义请参考数学书),而很可能是已经知道了相对关系的元素。元素之间的相对关系可以通过元素与根节点的相对关系计算出来是带权并查集可以应用的一个重要特征。路径压缩的时候除了把结点连到根上,还要更新边的权值。
下面再看一个例题,这个题和上面的例题很相似,难度更大一些。如果要提交代码,请访问[2]。
奇数偶数(来源:CEOI1999)
你和你的朋友在玩游戏。你的朋友写下一个只包含0和1的序列要你猜,序列的长度是n。你可以选择一个区间问你朋友其中的1的个数是奇数还是偶数,一共问了m次。你的任务是根据你朋友的回答猜出整个序列。
然而你推断你的朋友的回答可能有错,也就是说,不存在任何一个序列满足你朋友给出的所有答案。请找出一个x,是的从第一个答案到第x个答案是兼容的,即存在一个序列可以满足它们,但是第x+1个答案和前面的答案不兼容,即不存在一个序列满足从第1个到第x+1个答案。
输入:头两行是n<=1000000000和m<=5000,接下来m行两个整数x,y(x<=y)和一个字符串even或者odd,表示从x到y之间(包括x和y)1的个数是偶数还是奇数。
输出:一行,x的值。
分析:沿用上一题的思路,x和y之间的关系(1个数的奇偶性)已知,就把x和y合并。这里由于区间的有序性,合并应该有一定顺序。规定按照大的数更靠近 根,小的树更靠近叶子的方式合并,也就是说每次都把小的数接到大的数上面。还有一个问题,比如知道了[1,2]间1有偶数个,[3,4]间1有奇数个,那 么可以推出[1,4]间1有技术个,然而就并查集的实现来说,1的父亲为2,而2的父亲却不是4,也不是3,就好像区间断开了一样。做一个小小的改变可以 解决问题,把闭区间变成左闭右开区间即可,就是说每次输入y以后让y++。这样由[1,3)的奇偶性和[3,5)的奇偶性可以推出[1,5)的奇偶性,并 且两个区间通过3连了起来,并查集的实现就比闭区间方便多了。
假设right[x]=y表示某个以x为左端点的区间(包括x)右端点为y(不包括y),这里的关系就相当于y是x的父节点。parity[x]表示区间 [x,y)之间1的个数的奇偶性,偶数为0,奇数为1。规定如果right[x]=x(空区间),那么parity[x]=0(显然,有0个1,0是偶 数)。f(x,y)表示parent[x]=y的时候parity[x]的值,那么对任意正整数c,显然有f(x,y)= (f(x,c)+f(c,y))%2,我们又看到了带权并查集可以使用的明显特征。接下来的解法根第一个例题完全相似。需要注意的是,由于n的取值太大, 不能用数组处理并查集,可以设置一个结构,里面包含left,right,parity三个成员(分别对应x,y和parity[x]),然后用散列表来 处理即可。
下面是关于一些带权并查集的练习,除了最后一题基本上都比例题简单,会有多种解法。这里只简单提一下用带权并查集解的思路。
[1]http://acm.pku.edu.cn/JudgeOnline/problem?id=1182(例题1)
[2]http://acm.pku.edu.cn/JudgeOnline/problem?id=1733(例题2)
[3]http://acm.pku.edu.cn/JudgeOnline/problem?id=2492(基本同例1,边权0表示同性,1表示异性)
[4]http://acm.pku.edu.cn/JudgeOnline/problem?id=1703(同[3])
[5]http://acm.pku.edu.cn/JudgeOnline/problem?id=1988(同NOI银河英雄传说,边权表示相对距离)
[6]http://acm.pku.edu.cn/JudgeOnline/problem?id=2912(枚举裁判,其他同例题1)