1. 程式人生 > >[bzoj1455]羅馬遊戲_左偏樹_並查集

[bzoj1455]羅馬遊戲_左偏樹_並查集

gen ++ round span 大根堆 -s || () CP

羅馬遊戲 bzoj-1455

    題目大意:給你n個人,2種操作,m次操作:1.將i號士兵所在的集合的最小值刪除 2.合並i和j兩個士兵所在的團體

    註釋:$1\le n\le 10^6$,$1\le m \le 10^5$。

      想法:又是GXZlegend講課,可並堆中的左偏樹。了解一下:

        一個具有堆性質的二叉樹滿足任意一個節點x中,dis[lson[x]]>=dis[rson[x]],其中,dis表示當前節點一直走右兒子的最長步數。合並是遞歸合並,我們通過遞歸處理一兩個節點為根節點的左偏樹的合並,顯然左偏樹的子樹仍是左偏樹。我們直接將一顆子樹往另一顆子樹的有兒子上掛,這兩顆子樹根節點大的(默認大根堆)當做合並後的根節點即可。

        附上合並代碼... ...

void merge(int x,int y)
{
	if(!x) return y;
	if(!y) return x;
	if(val[x]<val[y]) swap(x,y);
	rson[x]=merge(rson[x],y);
	if(dis[rson[x]]>dis[lson[x]]) swap(rson[x],lson[x]);
	dis[x]=dis[rson[x]]+1;
	return x;
}

      至於這道題,我們用並查集維護每個人在哪個隊伍裏即可

    最後,附上醜陋的代碼... ...

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 1000010 
using namespace std;
int fa[N],rson[N],lson[N],dis[N],w[N];
bool k[N];
int find(int x)
{
	return fa[x]==x?x:(fa[x]=find(fa[x]));
}
int merge(int x,int y)
{
	if(!x) return y;
	if(!y) return x;
	if(w[x]>w[y]) swap(x,y);
	rson[x]=merge(rson[x],y);
	if(dis[rson[x]]>dis[lson[x]])
	{
		swap(lson[x],rson[x]);
	}
	dis[x]=dis[rson[x]]+1;
	return x;
}
inline int Kill(int x)
{
	if(k[x]) return 0;
	x=find(x);
	int t=merge(lson[x],rson[x]);
	fa[x]=t;
	fa[t]=t;
	k[x]=true;
	return w[x];
}
int main()
{
	int n,m;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&w[i]);
	}
	for(int i=1;i<=n;i++)
	{
		fa[i]=i;
	}
	char s[3];
	scanf("%d",&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%s",s+1);
		if(s[1]==‘M‘)	
		{
			int x,y;
			scanf("%d%d",&x,&y);
			if(k[x]||k[y]) continue;
			x=find(x),y=find(y);
			if(x!=y) fa[x]=fa[y]=merge(x,y);
			// fa[y]=x; 
		}
		else
		{
			int x;
			scanf("%d",&x);
			printf("%d\n",Kill(x));
		}
	}
	return 0;
}
/*
2
1 2
3
M 1 2
K 1
K 2
*/

    小結:錯誤都比較奇葩,在Kill的時候fa更新錯了,merge函數在寫的時候註意退出條件,不是任何時候都輸出x的。

[bzoj1455]羅馬遊戲_左偏樹_並查集