【BZOJ3510】首都 LCT维护子树信息+启发式合并

【BZOJ3510】首都

Description

在X星球上有N个国家,每个国家占据着X星球的一座城市。由于国家之间是敌对关系,所以不同国家的两个城市是不会有公路相连的。 
X星球上战乱频发,如果A国打败了B国,那么B国将永远从这个星球消失,而B国的国土也将归A国管辖。A国国王为了加强统治,会在A国和B国之间修建一条公路,即选择原A国的某个城市和B国某个城市,修建一条连接这两座城市的公路。 
同样为了便于统治自己的国家,国家的首都会选在某个使得其他城市到它距离之和最小的城市,这里的距离是指需要经过公路的条数,如果有多个这样的城市,编号最小的将成为首都。 
现在告诉你发生在X星球的战事,需要你处理一些关于国家首都的信息,具体地,有如下3种信息需要处理: 
1、A x y:表示某两个国家发生战乱,战胜国选择了x城市和y城市,在它们之间修建公路(保证其中城市一个在战胜国另一个在战败国)。 
2、Q x:询问当前编号为x的城市所在国家的首都。 
3、Xor:询问当前所有国家首都编号的异或和。

Input

第一行是整数N,M,表示城市数和需要处理的信息数。 
接下来每行是一个信息,格式如题目描述(A、Q、Xor中的某一种)。

Output

输出包含若干行,为处理Q和Xor信息的结果。

Sample Input

10 10
Xor
Q 1
A 10 1
A 1 4
Q 4
Q 10
A 7 6
Xor
Q 7
Xor

Sample Output

11
1
1
1
2
6
2

HINT

对于100%的数据,2<=N<=100000,1<=M<=200000。

题解:考虑每次将小的树合并到大的树上,这样每次大树的重心移动距离不会超过(小树的大小+1),那么我们只需要知道重心移动到了哪里。

可以确定的是,一棵树最多只有相邻的两个重心,所以我们如果想将b接到a的子树上,那么新的重心一定在(原重心-b)的这条链上,并且距离不超过(b树的大小+1),所以我们将对b进行access操作,在对原重心进行splay操作,这样的话这条链上的所有点就都在这棵splay里了,我们可以对splay进行中序遍历,就能将这些点都按顺序拿出来。

那么我们如何确定该以哪个为根呢?我们考虑根从一个点跳到它的儿子时是否会令答案更优,这就需要我们求出每个点的子树大小。我们只需要将当前点splay一下,然后它在LCT中的子树大小就是它在原树中的子树大小了(用LCT维护子树信息的方法不在赘述)。

本题的细节就在于要时刻注意splay,access和计算的顺序,即有时候的子树大小并不是真的大小,还需要进行一系列的操作(或者在进行某些操作后,子树大小就变了),还有别忘了中序遍历的时候需要pushdown。

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int maxn=100010;
int n,m,tot,nr,sum;
struct splay
{
	int sx,sl,ch[2],fa,rev;
}s[maxn];
int p[maxn];
char str[10];
bool isr(int x)	{return s[s[x].fa].ch[0]!=x&&s[s[x].fa].ch[1]!=x;}
void pushup(int x)	{s[x].sl=s[x].sx+s[s[x].ch[0]].sl+s[s[x].ch[1]].sl+1;}
void pushdown(int x)
{
	if(s[x].rev)
	{
		swap(s[x].ch[0],s[x].ch[1]),s[x].rev=0;
		if(s[x].ch[0])	s[s[x].ch[0]].rev^=1;
		if(s[x].ch[1])	s[s[x].ch[1]].rev^=1;
	}
}
void updata(int x)
{
	if(!isr(x))	updata(s[x].fa);
	pushdown(x);
}
void rotate(int x)
{
	int y=s[x].fa,z=s[y].fa,d=(x==s[y].ch[1]);
	if(!isr(y))	s[z].ch[y==s[z].ch[1]]=x;
	s[y].fa=x,s[x].fa=z,s[y].ch[d]=s[x].ch[d^1];
	if(s[x].ch[d^1])	s[s[x].ch[d^1]].fa=y;
	s[x].ch[d^1]=y;
	pushup(y),pushup(x);
}
void splay(int x)
{
	updata(x);
	while(!isr(x))
	{
		int y=s[x].fa,z=s[y].fa;
		if(!isr(y))
		{
			if((y==s[z].ch[0])^(x==s[y].ch[0]))	rotate(x);
			else	rotate(y);
		}
		rotate(x);
	}
}
void access(int x)
{
	for(int y=0;x;splay(x),s[x].sx-=s[y].sl-s[s[x].ch[1]].sl,s[x].ch[1]=y,pushup(x),y=x,x=s[x].fa);
}
void maker(int x)
{
	access(x),splay(x),s[x].rev^=1;
}
void link(int x,int y)
{
	maker(x),access(y),splay(y),s[y].sx+=s[x].sl,s[x].fa=y,pushup(y);
}
int findr(int x)
{
	access(x),splay(x),pushdown(x);
	while(s[x].ch[0])	x=s[x].ch[0],pushdown(x);
	return x;
}
void query(int x,int y)
{
	if(!x)	return ;
	pushdown(x);
	query(s[x].ch[0],y);
	if(p[0]>=y)	return ;
	p[++p[0]]=x;
	if(p[0]>=y)	return ;
	query(s[x].ch[1],y);
}
int rd()
{
	int ret=0,f=1;	char gc=getchar();
	while(gc<‘0‘||gc>‘9‘)	{if(gc==‘-‘)f=-f;	gc=getchar();}
	while(gc>=‘0‘&&gc<=‘9‘)	ret=ret*10+gc-‘0‘,gc=getchar();
	return ret*f;
}
int main()
{
	//freopen("bz3510.in","r",stdin);
	n=rd(),m=rd();
	int i,j,a,b,c,d,e,sc,sd;
	for(i=1;i<=n;i++)	s[i].sl=1,sum^=i;
	for(i=1;i<=m;i++)
	{
		scanf("%s",str);
		if(str[0]==‘X‘)	printf("%d\n",sum);
		if(str[0]==‘Q‘)	printf("%d\n",findr(rd()));
		if(str[0]==‘A‘)
		{
			a=rd(),b=rd(),c=findr(a),splay(c),d=findr(b),splay(d),sc=s[c].sl,sd=s[d].sl;
			if(sc<sd||(sc==sd&&c>d))	swap(c,d),swap(a,b),swap(sc,sd);
			link(b,a),access(b),splay(c),p[0]=0,tot=sc+sd,query(c,sd+1),nr=c;
			for(j=1;j<=p[0];j++)
			{
				splay(p[j]);
				e=s[p[j]].sx+1+(s[p[j]].ch[1]>0?s[s[p[j]].ch[1]].sl:0);
				if(tot-e<e||(tot-e==e&&p[j]<=nr))	nr=p[j];
				else	break;
			}
			maker(nr),sum^=nr,sum^=c,sum^=d;
		}
	}
	return 0;
}
时间: 2024-11-05 04:09:45

【BZOJ3510】首都 LCT维护子树信息+启发式合并的相关文章

bzoj3510 首都 LCT 维护子树信息+树的重心

题目传送门 https://lydsy.com/JudgeOnline/problem.php?id=3510 题解 首先每一个连通块的首都根据定义,显然就是直径. 然后考虑直径的几个性质: 定义:删去这个点以后剩下的连通块最大的最小的点为重心. 一棵树最多只能有两个相邻的直径: 一棵树的重心到一棵树中所有点的距离和最小.(这个也是题目的条件转化为重心的原因) 两棵树的并的重心在两棵树各自的重心的连线上. 一棵树添加或者删除一个节点,树的重心最多只移动一条边的位置. 有了这些性质,我们可以发现,

【LCT维护子树信息】uoj207 共价大爷游长沙

这道题思路方面就不多讲了,主要是通过这题学一下lct维护子树信息. lct某节点u的子树信息由其重链的一棵splay上信息和若干轻儿子子树信息合并而成. splay是有子树结构的,可以在rotate,access的时候由儿子update到父亲,而轻儿子的信息update不上来,需要另外记一下. 记sum[x]为我们要求的子树信息,xu[x]为x的轻儿子的子树信息. (即,xu[x]由轻儿子的sum更新,sum[x]由xu[x]和splay子树上的儿子的sum更新. 这样我们就可以完整地用lct维

【bzoj4530】[Bjoi2014]大融合 LCT维护子树信息

题目描述 小强要在N个孤立的星球上建立起一套通信系统.这套通信系统就是连接N个点的一个树. 这个树的边是一条一条添加上去的.在某个时刻,一条边的负载就是它所在的当前能够联通的树上路过它的简单路径的数量. 例如,在上图中,现在一共有了5条边.其中,(3,8)这条边的负载是6,因为有六条简单路径2-3-8,2-3-8-7,3-8,3-8-7,4-3-8,4-3-8-7路过了(3,8). 现在,你的任务就是随着边的添加,动态的回答小强对于某些边的负载的询问. 输入 第一行包含两个整数N,Q,表示星球的

Loj 2230. 「BJOI2014」大融合 (LCT 维护子树信息)

链接:https://loj.ac/problem/2230 思路: 设立siz数组保存虚点信息,sum表示总信息 维护子树信息link操作和access操作需要进行一些改动 可参考博客:https://www.cnblogs.com/GXZlegend/p/7061458.html 实现代码; #include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include&l

[BJOI2014]大融合 LCT维护子树信息

Code: #include <cstdio> #include <algorithm> #include <cstring> #include <string> using namespace std; void setIO(string a){freopen((a+".in").c_str(),"r",stdin);} #define maxn 100009 #define ll long long int n,q

[XSY 1556] 股神小D LCT维护子树信息

实现 1 2 #include <cstdio> 3 #include <cstring> 4 #include <cstdlib> 5 #include <cctype> 6 #include <algorithm> 7 #include <vector> 8 using namespace std; 9 #define F(i, a, b) for (register int i = (a); i <= (b); i++)

bzoj 2809 可并堆维护子树信息

对于每个节点,要在其子树中选尽量多的节点,并且节点的权值和小于一个定值. 建立大根堆,每个节点从儿子节点合并,并弹出最大值直到和满足要求. 1 /************************************************************** 2 Problem: 2809 3 User: idy002 4 Language: C++ 5 Result: Accepted 6 Time:1224 ms 7 Memory:6664 kb 8 **************

BZOJ.3510.首都(LCT 启发式合并 树的重心)

题目链接 BZOJ 洛谷 详见这. 求所有点到某个点距离和最短,即求树的重心.考虑如何动态维护. 两棵子树合并后的重心一定在两棵树的重心之间那条链上,所以在合并的时候用启发式合并,每合并一个点检查sz[]大的那棵子树的重心(记为root)最大子树的sz[] * 2是否>n: 若>n,则向fa移动一次(先把合并点Splay到根).重心还一定是在sz[]大的那棵子树中,且移动次数不会超过sz[]小的子树的点数(所以总移动次数不会超过O(n)?). 复杂度 \(O(nlog^2n)\) 具体实现..

【BZOJ-2888】资源运输 LCT + 启发式合并

2888: 资源运输 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 63  Solved: 33[Submit][Status][Discuss] Description 小Y盯上了最近发行的即时战略游戏——ResourceTransport.但在前往通关之路的道路上,一个小游戏挡住了小Y的步伐.“国家的本质是生产与收集资源”是整款游戏的核心理念,这个小游戏也不例外.简单的说,用户需要管理一个国家,使其繁荣富强. 一个国家含有N个城市,游戏开始时