罗马游戏 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的。