【Codeforces 1137F Matches Are Not a Child's Play】【LCT】

题意

有一棵 n n n个节点的树,定义点燃这样一个过程:每次选一个编号最小的叶节点删掉,并把该节点的编号加入序列末端,从而得到一个长度为 n n n的序列。现在有 q q q个操作,每次可以把某个节点的编号变为最大,或是询问一个点在燃烧得到的序列的第几位,或是询问两个点谁的位置靠前。
n , q ≤ 200000 n,q\le200000 n,q200000

分析

显然第三种询问可以通过调用两次第二种询问来实现。
假设已经知道当前的燃烧序列,设编号最大的点为树的根,当把某个点的编号变为最大,不难发现燃烧序列中只有新根到旧根路径上所有点的位置被调到最后,其余点的相对位置不变。
如果我们把一次赋值操作变为到根路径上的染色,那么一个点的答案就是颜色比他小的点的数量以及和他同种颜色的点中,比他先被删掉的点数。
用LCT来维护这棵树,一次染色就相当于 a c c e s s access access操作,用一棵权值树状数组来维护前者,后者就相当于把该点 s p l a y splay splay到根后右子树的大小。
时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>

const int N=200005;
const int maxn=200000;

int n,q,cnt,last[N],deg[N],rk[N],c1[N],c2[N],tim;
struct edge{int to,next;}e[N*2];
struct tree{int l,r,fa,s,col;bool rev;}t[N];
std::priority_queue<int> que;

void addedge(int u,int v)
{
	e[++cnt].to=v;e[cnt].next=last[u];last[u]=cnt;
	e[++cnt].to=u;e[cnt].next=last[v];last[v]=cnt;
}

void pre()
{
	for (int i=1;i<=n;i++) if (deg[i]==1) que.push(-i),deg[i]=-1;
	for (int i=1;i<=n;i++)
	{
		int x=-que.top();que.pop();
		rk[x]=i;
		for (int j=last[x];j;j=e[j].next) if (deg[e[j].to]>0)
		{
			deg[e[j].to]--;
			if (deg[e[j].to]<=1) que.push(-e[j].to),deg[e[j].to]=-1;
		}
	}
}

void dfs(int x,int fa)
{
	t[x].s=1;
	for (int i=last[x];i;i=e[i].next) if (e[i].to!=fa) t[e[i].to].fa=x,dfs(e[i].to,x);
}

int find(int *c,int x)
{
	int ans=0;
	while (x) ans+=c[x],x-=x&(-x);
	return ans+c[0];
}

void ins(int *c,int x,int y)
{
	if (!x) c[0]+=y;
	else while (x<=maxn) c[x]+=y,x+=x&(-x);
}

bool is_root(int x)
{
	return x!=t[t[x].fa].l&&x!=t[t[x].fa].r;
}

void updata(int x)
{
	t[x].s=t[t[x].l].s+t[t[x].r].s+1;
}

void remove(int x)
{
	if (!is_root(x)) remove(t[x].fa);
	if (!t[x].rev) return;
	std::swap(t[x].l,t[x].r);t[x].rev=0; 
	if (t[x].l) t[t[x].l].rev^=1;
	if (t[x].r) t[t[x].r].rev^=1;
}

void rttl(int x)
{
	int y=t[x].r;t[y].col=t[x].col;
	t[x].r=t[y].l;t[t[y].l].fa=x;
	if (x==t[t[x].fa].l) t[t[x].fa].l=y;
	else if (x==t[t[x].fa].r) t[t[x].fa].r=y;
	t[y].fa=t[x].fa;
	t[y].l=x;t[x].fa=y;
	updata(x);updata(y);
}

void rttr(int x)
{
	int y=t[x].l;t[y].col=t[x].col;
	t[x].l=t[y].r;t[t[y].r].fa=x;
	if (x==t[t[x].fa].l) t[t[x].fa].l=y;
	else if (x==t[t[x].fa].r) t[t[x].fa].r=y;
	t[y].fa=t[x].fa;
	t[y].r=x;t[x].fa=y;
	updata(x);updata(y);
}

void splay(int x)
{
	remove(x);
	while (!is_root(x))
	{
		int p=t[x].fa,q=t[p].fa;
		if (is_root(p))
		{
			if (x==t[p].l) rttr(p);
			else rttl(p);
			break;
		}
		if (x==t[p].l)
			if (p==t[q].l) rttr(q),rttr(p);
			else rttr(p),rttl(q);
		else
			if (p==t[q].r) rttl(q),rttl(p);
			else rttl(p),rttr(q);
	}
}

void access(int x)
{
	int y=0;
	while (x)
	{
		splay(x);
		if (t[x].r) t[t[x].r].col=t[x].col;
		ins(c1,t[x].col,-t[x].s+t[t[x].r].s);
		if (!t[x].col) ins(c2,rk[x],1);
		t[x].r=y;updata(x);
		y=x;x=t[x].fa;
	}
	t[y].col=tim;
	ins(c1,tim,t[y].s);
}

void make_root(int x)
{
	access(x);splay(x);t[x].rev^=1;
}

int query(int x)
{
	splay(x);
	if (!t[x].col) return rk[x]-find(c2,rk[x]);
	else return find(c1,t[x].col-1)+t[t[x].r].s+1;
}

int main()
{
	scanf("%d%d",&n,&q);
	for (int i=1;i<n;i++)
	{
		int x,y;scanf("%d%d",&x,&y);
		addedge(x,y);
		deg[x]++;deg[y]++;
	}
	pre();
	dfs(n,0);
	c1[0]=n;
	while (q--)
	{
		char ch[10];scanf("%s",ch);
		if (ch[0]=='u')
		{
			int x;scanf("%d",&x);
			tim++;
			make_root(x);
		}
		else if (ch[0]=='w')
		{
			int x;scanf("%d",&x);
			printf("%d\n",query(x));
		}
		else
		{
			int x,y;scanf("%d%d",&x,&y);
			printf("%d\n",query(x)<query(y)?x:y);
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值