【並查集】黑魔法師之門

dnldnth發表於2012-08-19

黑魔法師之門

(magician.pas/c/cpp)

題目描述

經過了16個工作日的緊張忙碌,未來的人類終於收集到了足夠的能源。然而在與Violet星球的戰爭中,由於Z副官的愚蠢,地球的領袖applepi被邪惡的黑魔法師Vani囚禁在了Violet星球。為了重啟Nescafé這一巨集偉的科技工程,人類派出了一支由XLkPoet_shylydrainbowcat三人組成的精英隊伍,穿越時空隧道,去往Violet星球拯救領袖applepi

applepi被囚禁的地點只有一扇門,當地人稱它為“黑魔法師之門”。這扇門上畫著一張無向無權圖,而開啟這扇門的密碼就是圖中每個點的度數大於零且都是偶數的子圖的個數對1000000009取模的值。此處子圖 (V, E) 定義為:點集V和邊集E都是原圖的任意子集,其中E中的邊的端點都在V中。

但是Vani認為這樣的密碼過於簡單,因此門上的圖是動態的。起初圖中只有N個頂點而沒有邊。Vani建造的門控系統共操作M次,每次往圖中新增一條邊。你必須在每次操作後都填寫正確的密碼,才能夠開啟黑魔法師的牢獄,去拯救偉大的領袖applepi

輸入格式

第一行包含兩個整數NM

接下來M行,每行兩個整數AB,代表門控系統新增了一條無向邊 (A, B)

輸出格式

輸出一共M行,表示每次操作後的密碼。

樣例輸入

4 8

3 1

3 2

2 1

2 1

1 3

1 4

2 4

2 3

樣例輸出

0

0

1

3

7

7

15

31

樣例說明

第三次新增之後,存在一個滿足條件的子圖 {1, 2, 3}(其中1, 2, 3是資料中邊的標號)。

第四次新增之後,存在三個子圖 {1, 2, 3}{1, 2, 4}{3, 4}

……

資料範圍與約定

對於30% 的資料,N, M10

對於100% 的資料,N200000M300000

 

 

題目中定義的子環,經過分析可以發現,它就是一個一個環。

我們可以求出元環個數n來,再根據排列組合求出元環組合(子圖)總數為2^n-1.

 

我考試的時候卡在了後面那句話上,因此就沒做出來。

即子圖總數為2^n-1,因為我以為這些環必須是有交點的。實際上不用,再仔細看一下定義,可以明白,下面這種情況,其實他的子圖個數是3而不是2。

當時我就卡在了這裡,我想找到所有有交點的環,再用排列組合來算,但是這實際上很難處理。。。都是題意沒有搞清楚的過。

 

 

多讀題,仔細理解,總會有收穫的。

 

另一個我沒有突破的,實際上可能和以上問題有關,限制了自己的想法。其實,我們從它的邊是一條一條加入的,能夠看出一些端倪。另外貌似noip要求的線上演算法不多,常常會用到並查集。而這道題,確實是要用到並查集。

 

和環有關的演算法,瞭解到並查集的優勢後,現在能夠使用的就有三個了。Tarjan,floyd,並查集。(Tarjan放在這裡其實是比較牽強的)

而並查集,我們只需要在加入新的邊之前,判斷是否已經在一個聯通分量裡,如果在,增加一條邊,必定會增加一個元環。

 

 

題解裡的思路和我的不同。順便貼上題解。

黑魔法師之門

題目來源:XLk 提供

大致思路:

實際上每次操作後的答案就是2^(圖中”元”環的個數)。

元環的意思如右圖所示,(1-2-3-4-1)和(3-4-5-3) 是元環,

1-2-3-5-4-1 不是,因為它可以看做由上述的兩個環合成。
因為一個環裡每個點的度數都是大於零的偶數,我們可以這

樣來構造答案:每個環有選和不選兩種選擇,如果選擇了該

環,那麼環上所有邊的“選擇次數”+1。最後取所有“選擇次數”為奇數的邊構成一個邊集,

就是一個答案。可以證明這樣構造出來的解不重複且涵蓋了所有情況。因此答案就是2^(圖

中”元”環的個數)。實現方法非常簡單,只需要一個並查集即可。

具體實現方法:

並查集維護連通性,初始化ans=1。

加入一條邊(x,y)時,如果x 和y 在同一集合內,ans*=2。

每次詢問輸出ans-1。
時間複雜度O(M α(N)),α(N)代表並查集的複雜度。

 

 

#include <cstdio>
#include <iostream>
using std::cout;

long fa[200010];

long getroot(long l)
{
	if (fa[l] == l)
		return fa[l];
	return fa[l] = getroot(fa[l]);
}

inline void merge(long a,long b)
{
	fa[getroot(a)] = getroot(b);
}

long getint()
{
	long rs=0;bool sgn=1;char tmp;
	do tmp=getchar();
	while (!isdigit(tmp)&&tmp-'-');
	if (tmp=='-'){sgn=0;tmp=getchar();}
	do rs=(rs<<3)+(rs<<1)+tmp-'0';
	while (isdigit(tmp=getchar()));
	return sgn?rs:-rs;
}

int main()
{
	freopen("magician.in","r",stdin);
	freopen("magician.out","w",stdout);
	long n = getint();
	long m = getint();
	long ans = 1;
	for (long i=1;i<n+1;i++)
		fa[i] = i;
	for (long i=1;i<m+1;i++)
	{
		long a = getint();
		long b = getint();
		if (getroot(a) == getroot(b))
		{
			ans <<= 1;
			if (ans > 1000000008)
				ans -= 1000000009;
		}
		else
			merge(a,b);
		printf("%ld\n",ans-1);
	}
	return 0;
}

相關文章