$Luogu$ $P1196$ $[NOI2002]$ 银河英雄传说

链接

背景

(CCF) (NOI) (2002) (T1)(Luogu) (P1196/Vijos1443)

题意

给出 (30000) 艘战舰排成一行(从 (1)(30000) 编号),每艘独占一列。给出 (T) 条指令,形如 (M x y)(C x y) ,前一个表示第 (x) 号战舰所在的一列战舰整体平移到第 (y) 号战舰所在的一列战舰的后方,后一个表示查询第 (x) 号战舰和第 (y) 号战舰是否在同一列,若是的话还要查询两艘战舰之间隔着几艘战舰。

解法

考虑把每个战舰当做一个集合,用并查集维护当前战舰所在的集合的首个元素。当然,仅仅这样是不够的。由于要查询两艘战舰之间隔着几艘战舰,考虑维护每艘战舰 (x) 到首个元素(列首)的距离 (d_x) 。对于一个 (M) 操作,相当于是把 (x) 的代表元素作为 (y) 所在集合的一个节点,于是向 (y) 的代表元素合并 (x) 的代表元素即可。
考虑合并的过程怎么更新 (d_x) 。求 (d_x) 的过程就是把它到根节点之间的所有边权求个和。于是魔改一下查询代表元素的函数即可。合并完之后考虑 (d_x) 已经被更新了,新的值是 (y) 所在集合的总战舰数,于是还要记一个 (siz_x) 表示节点 (x) 所在集合的元素数。也要动态维护。
然后是 (C) 操作。显然直接查询 (x) 的代表元素和 (y) 的代表元素是否相同即可判定是否在同一列。然后两艘战舰之间隔着的战舰数就是 (left| d_x-d_y ight| -1) ,即两点到代表元素的距离之差减去一个元素的值。

(trick)

为每个节点 (i) 安排一个权值 (d_i) 用来回答询问或者判定合法性。注意 (d) 数组的维护方法(先递归找代表再累加权值)。

代码

$View$ $Code$

//省略头文件
using namespace std;
inline int read()
{
	int ret=0,f=1;
	char ch=getchar();
	while(ch>'9'||ch<'0')
	{
		if(ch=='-')
			f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		ret=(ret<<1)+(ret<<3)+ch-'0';
		ch=getchar();
	}
	return ret*f;
}
int T,x,y,fa[30005],d[30005],siz[30005];
char op;
int get(int x)
{
	if(x==fa[x])
		return x;
	int rt=get(fa[x]);
	d[x]+=d[fa[x]];
	return fa[x]=rt;
}
int main()
{
	T=read();
	for(register int i=1;i<=30000;i++)
	{
		fa[i]=i;
		siz[i]=1;
	}
	while(T--)
	{
		scanf("%c",&op);
		x=read();
		y=read();
		if(op=='C')
		{
			int fx=get(x),fy=get(y);
			if(fx!=fy)
				printf("-1
");
			else
				printf("%d
",abs(d[x]-d[y])-1);
		}
		else
		{
			int fx=get(x),fy=get(y);
			fa[fx]=fy;
			d[fx]=siz[fy];
			siz[fy]+=siz[fx];
		}
	}
	return 0;
}
原文地址:https://www.cnblogs.com/Peter0701/p/11808785.html