【dfs序】【线段树】树链剖分换根

【题目描述】
给定一棵大小为 n 的有根点权树,支持以下操作:
• 换根
• 修改点权
• 查询子树最小值
【输入】
第一行两个整数 n, Q ,分别表示树的大小和操作数。
接下来n行,每行两个整数f,v,第i+1行的两个数表示点i的父亲和点i的权。保
证f < i。如 果f = 0,那么i为根。输入数据保证只有i = 1时,f = 0。
接下来 m 行,为以下格式中的一种:
• V x y表示把点x的权改为y
• E x 表示把有根树的根改为点 x
• Q x 表示查询点 x 的子树最小值
【输出】
对于每个 Q ,输出子树最小值。

【样例输入】
3 7
0 1
1 2
1 3
Q 1
V 1 6
Q 1
V 2 5
Q 1
V 3 4
Q 1
【样例输出】
1
2
3
4
【提示】
对于 100% 的数据:n, Q ≤ 10^5。

【思路】

对于这个题,我们可以写树链剖分。但是出于代码量和调试难度的考虑,我选择了dfs序来维护子树信息。对于维护子树最小值和单点修改,我们可以用线段树在dfs序上维护。那么目前的问题就在于换根。显然,我们不可能真正的换根重新dfs或者树链剖分一次。那么问题就在于利用原树上的信息来得到答案。对于维护子树和或者子树最小值的这一类问题,我们常常分三类情况讨论。
1.如果root不在u的子树内(lca(root,u)!=u),那么换根后的答案就是原树的答案。
2.如果root就是u(root==u),那么直接查询整颗子树。
3.如果root在u的子树内(lca(root,u)==u),我们发现root到u路径上经过的的最后一个节点v(也就是u的一个儿子)的原树中的子树是u换根后子树的以整棵树为全集的补集。也就是说,我们只要不考虑v的原树中的子树的前提下查询整棵树的其他部分就行了。如果是维护子树和,我们可以用整棵树的子树和减去v的子树的和。这里我们需要维护最值,不满足区间减法。因此我们考虑区间加法。显然在dfs序中,我们只需要合并区间[1,st[v]-1][ed[v]+1,n]就可以得到u换根后字数的答案。而对于v,我们可以在求lca(u,root)的时候顺便存下来。
代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#include<queue>
#define re register
#define lc (p<<1)
#define rc (p<<1|1)
using namespace std;
int n,m,a,b;
const int N=2e5+1;
int f[N];
int g[N];
int nxp[N<<2|1];
int cnt=0,tot=0;
struct ndeo{
	int u,v;
}e[N];
int idx=0;
struct tree{
	int l,r;
	int mn;
}t[N];
inline void add(int u,int v)
{
	e[++cnt].u=u;
	e[cnt].v=v;
	nxp[cnt]=f[u];
	f[u]=cnt;
}
int fa[N][20];
int st[N];
int ed[N];
int dep[N];
int rev[N];
void dfs(int u)
{
	st[u]=++idx;rev[idx]=u;
	for(int re i=1;(1<<i)<=dep[u];i++)
		fa[u][i]=fa[fa[u][i-1]][i-1];
	for(int re i=f[u];i;i=nxp[i])
	{
		int v=e[i].v;
		if(!dep[v])
		{
			dep[v]=dep[u]+1;
			fa[v][0]=u;
			dfs(v);
		}
	}
	ed[u]=idx;
}
typedef pair<int,int>T;
T lca(int a,int b)
{
	if(dep[a]<dep[b])swap(a,b);
	int t=dep[a]-dep[b];t--;
	for(int re i=0;(1<<i)<=t;i++)
		if(t&(1<<i))a=fa[a][i];
	if(fa[a][0]==b)return make_pair(fa[a][0],a);
	for(int re i=18;i>=0;i--)
	{
		if(fa[a][i]!=fa[b][i])
		{
			a=fa[a][i];
			b=fa[b][i];
		}
	}
	return make_pair(fa[a][0],a);
}
char p;
int root=0;
void build(int p,int l,int r)
{
	t[p].l=l;
	t[p].r=r;
	if(l==r)
	{
		t[p].mn=g[rev[l]];
		return;
	} 
	int mid=(l+r)>>1;
	build(lc,l,mid);
	build(rc,mid+1,r);
	t[p].mn=min(t[lc].mn,t[rc].mn); 
}
void change(int p,int k,int v)
{
	int l=t[p].l;
	int r=t[p].r;
	if(l==r)
	{
		t[p].mn=v;
		return;
	}
	int mid=(l+r)>>1;
	if(k<=mid)change(lc,k,v);
	else if(k>mid)change(rc,k,v);
	t[p].mn=min(t[lc].mn,t[rc].mn); 
}
int ans=0x7f7f7f7f;
void query(int p,int ql,int qr)
{
	if(qr<ql)return;
	if(ql<=t[p].l&&t[p].r<=qr)
	{
		ans=min(ans,t[p].mn);
		return;
	}
	int mid=(t[p].l+t[p].r)>>1;
	if(ql<=mid)query(lc,ql,qr);
	if(qr>mid)query(rc,ql,qr);
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int re i=1;i<=n;i++)
	{
		scanf("%d%d",&a,&g[i]);
		if(!a)
		{
			root=i;
			continue;
		}
		add(a,i);
		add(i,a);
	}
	dep[root]=1;
	dfs(root);
	build(1,1,idx);
	for(int re i=1;i<=m;i++)
	{
		p=getchar();
		while(p!='Q'&&p!='V'&&p!='E')p=getchar();
		if(p=='V')scanf("%d%d",&a,&b),change(1,st[a],b);
		else if(p=='E')scanf("%d",&root);
		else
		{
			scanf("%d",&a);
			if(root==a)
			{
				ans=0x7f7f7f7f;
				query(1,1,idx);
				printf("%d\n",ans);
				continue;
			}
			T s=lca(a,root);
			if(s.first!=a)
			{
				ans=0x7f7f7f7f;
				query(1,st[a],ed[a]);
				printf("%d\n",ans);
				continue;
			}
			else
			{
				ans=0x7f7f7f7f;
				query(1,1,st[s.second]-1);
				int ans1=ans;
				ans=0x7f7f7f7f;
				query(1,ed[s.second]+1,idx);
				printf("%d\n",min(ans,ans1));
			}
		}
	}
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值