1. 程式人生 > >LG-P2342 疊積木

LG-P2342 疊積木

P2342 疊積木
題目連結
題目背景
Cube Stacking, 2004 Open

題目描述
約翰和貝西在疊積木。共有30000塊積木,編號為1到30000。一開始,這些積木放在地上,自然地分成N堆。貝西接受約翰的指示,把一些積木疊在另一些積木的上面。一旦兩塊積木相疊, 彼此就再也不會分開了,所以最後疊在一起的積木會越來越高。約翰讓貝西依次執行P條操作,操作分為兩種:

? 第一種是移動操作,格式為“移動X到Y的上面”。X和Y代表兩塊積木的編號,意思是將X所的那堆積木,整體疊放到Y所在的那堆積木之上;

? 第二種是統計操作,格式為“統計Z下方的積木數量”。Z代表一塊積木的編號,意思是貝西需要報告在編號為Z的積木之下還有多少塊積木

請編寫一個程式,幫助貝西回答每條統計問題。

輸入格式:
? 第一行:單個整數:P,1 ≤ P ≤ 10^5

? 第二行到第P + 1行:每行描述一條命令,如果這行開頭的字母是 M,代表一條移動命令,後面的兩個整數代表上文中的X和Y;如果開頭字母是 C,代表一條統計命令。後面的整數代表上文中的Z,保證所有的移動命令都有意義,X和Y不會已經出現在同一堆積木裡

輸出格式:
? 對每一個統計命令,輸出正確回答,用換行符分開每個查詢的結果

輸入樣例:
6
M 1 6
C 1
M 2 4
M 2 6
C 3
C 4
輸出樣例:
1
0
2
說明
第一次查詢時, 1 下面只有一個 6;第二次查詢時, 3 下面沒有任何積木;第三次查詢時,4 下面有兩塊積木:1 和 6

題解
真不知道洛谷題時是不是沒人會加LG字首

這題最容易想到的就是利用並查集來求。

用C陣列表示每個箱子下面的箱子個數。
我們不可能每次移動後去更新每個箱子。
顯然,那樣效率太低了。

解決方案之一就是每次推的時候(即刷並查集的時候)修正。

想象一下(或者開啟你的mspaint.exe),如果現在我們把一個積木 1 移動到另一個積木 2 上面,我們應該把誰設為父節點?

經過嘗試你會發現,如果把 1 設為父節點,也就是把最頂上的設為父節點。
假設 1 為這堆最頂上的,2 為這堆最底下的。
那麼當你把這堆東西放到另一堆東西上時,你會發現 2 以下下的積木數量修正以後無法傳遞給上面的積木。(路徑壓縮後的並查集是多叉樹,難以繼續修正)
而詢問 2 上面積木的時候也不知道該通過誰來修正一趟。

所以,我們還是把最底層的設為根節點。

接下來,我們考慮一下怎麼修正。

如果把 1 移到以 2 為堆底的那堆上面,又把 2 移動到其他東西上面,此時我們再求 1 的父節點時的便利過程:
1->2->fa[2]->……end,最終 1 的父節點會被修正成 end。
(壓縮路徑後為多叉樹)
我們把一棵以 2 為根樹接到一棵以 end 為根的樹上(接在end上),然後需要在壓縮路徑的同時修正答案。
(2 的答案以及修正了)

我們從 2 的一個子節點開始看,可以發現我們只需把 2 的答案疊加給它就好了。
因為 c[2] 原本為 0(因為它是堆底,下面不可能有東西),這樣也保證了我們重複求一個點祖先的時候不會把答案疊加重複。

那麼,如果是 2 的兒子的兒子呢?
既然它不是 2 的直屬,那麼說明這條路徑沒壓縮過。假設 2 有個兒子 x,這個 x 也有個兒子 y,顯然,y 的祖先從 x 變成 2 的時候,也並沒有把增加的代價疊加過來,而增加的代價正好是 x 當前的值。

真妙啊,此時會發現,或許給 C 陣列換一個定義就可以更好地理解。

C[x] 表示:節點 x 的子節點的懶惰標記。

程式碼

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=3e4+5;
int n=3e4,m,fa[maxn],ans[maxn],H[maxn];
int rad()
{
	int ret=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
	return ret*f;
}
int get(int x)
{
	return x!=fa[x]?get(fa[x]),ans[x]+=ans[fa[x]],fa[x]=fa[fa[x]]:x;
}
int main()
{
	m=rad();
	for (int i=1;i<=n;++i) fa[i]=i,ans[i]=0,H[i]=1;
	char ch;
	for (int i=1;i<=m;++i)
	{
		cin>>ch;
		if (ch=='M')
		{
			int x=get(rad()),y=get(rad());
			if (x==y) continue;
			fa[x]=y;
			ans[x]+=H[y];
			H[y]+=H[x];
		}else
		{
			int x=rad();
			get(x);
			printf("%d\n",ans[x]);
		}
	}
	return 0;
}