CF1083C Max Mex

本文探讨了一种高效的算法,用于在给定的树状结构中维护非负整数集合的 mex 值。通过线段树和欧拉序,作者解决了在交换节点值和路径查询操作中的复杂问题,以最大化路径上 mex 的最大值。关键思想包括使用两点间距离判断路径关系和线程树结构的更新维护。
摘要由CSDN通过智能技术生成

CF1083C Max Mex

题意:
对于一个非负整数集合 S S S,定义 m e x ( S ) mex(S) mex(S) 为没有在集合 S S S 中出现的最小非负整数。

例如,对于非负整数集合 S = { 0 , 1 , 3 } S = \{0, 1, 3\} S={0,1,3},没有在集合 S S S 中出现的最小非负整数为 2 2 2,因此 m e x ( { 0 , 1 , 3 } ) = 2 mex(\{0, 1, 3\}) = 2 mex({0,1,3})=2

给定一棵包含 n n n 个结点的树,每个结点对应着一个非负整数 p i p_i pi,你需要实现 q q q 次操作,操作包含如下两种类型:

  • 1 i j:将结点 i i i j j j 对应的非负整数 p i p_i pi p j p_j pj 交换。
  • 2:在树上寻找一条简单路径,使得该条路径包含的所有结点(包括路径的端点)对应的非负整数构成的集合的 m e x mex mex 值尽可能大。

对于每个操作 2,输出 m e x mex mex 的最大值。

1 ≤ n , q ≤ 2 × 1 0 5 1 \leq n, q \leq 2 \times 10^5 1n,q2×105,输入保证给出的图是一棵树,且所有结点对应的非负整数 { p i } \{p_i\} {pi} 构成了一个 0 ∼ n − 1 0 \sim n - 1 0n1 的排列。

思路:
首先考虑暴力的做法,想要令 M e x Mex Mex 最大,当然要按照点权从小把点加进当前的路径,如果能加进来,那么 M e x Mex Mex 就会变大 1 1 1
可以发现一个路径的 M e x Mex Mex和点加入的顺序是没关系的,所以可以把一个区间的数一起加进来。
考虑用线段树维护,线段树的一个结点代表 包含这个区间所有数的一条最短路径,如果不存在这样的路径则为 ( − 1 , − 1 ) (-1,-1) (1,1)
考虑合并两个路径,是很麻烦的,但是因为树上两点之间路径是唯一的,所以如果路径的两个端点 u , v u,v u,v都在 另一条路径上了,那么 路径 ( u , v ) (u,v) (u,v)也在另一条路径上。
现在考虑把一个点加入一条路径,可以用三点之间的距离来判断 三点是否在一条路径上。
三点之间关系只有一下四种,分情况讨论即可。
在这里插入图片描述

求两点之间的距离 需要求 l c a lca lca,但是合并的次数比较多,不支持用倍增去求,但是可以用 S T ST ST表+欧拉序,就可以在 O ( l o g ) O(log) O(log)预处理之后, O ( 1 ) O(1) O(1)去查询答案。

妙妙子题。
具体看代码。

代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long 

int n,m;
int pos[200050];
int val[200050];

vector<int>v[200050];
int s[400060],len=0;
int dep[200050];
int Log[400060],f[400060][20+5];
int id[400050][20+5];
int st[200050],ed[200050];
void dfs(int x,int pre){
	s[++len]=x;dep[x]=dep[pre]+1;
	st[x]=len;ed[x]=len;
	for(int i=0;i<v[x].size();i++){
		int to=v[x][i];
		if(to==pre)continue;
		dfs(to,x);
		s[++len]=x;
		ed[x]=len;
	}
}
void Loginit(int n){
	Log[0]=-1;//这样Log[1]=0;
	for(int i=1;i<=n;i++){
		f[i][0]=dep[s[i]];//设置边界条件
		id[i][0]=s[i];
		Log[i]=Log[i>>1]+1;
	}
}
void rmq(){
	for(int k=1;k<=20;k++){
		for(int i=1;i+(1<<k)-1<=len;i++){
			if(f[i][k-1]>f[i+(1<<(k-1))][k-1]){
				f[i][k]=f[i+(1<<(k-1))][k-1];
				id[i][k]=id[i+(1<<(k-1))][k-1];
			}else{
				f[i][k]=f[i][k-1];
				id[i][k]=id[i][k-1];
			}
		}
	}
}
int lca(int a,int b){
	int l=min(st[a],st[b]),r=max(ed[a],ed[b]);
	int deep=Log[r-l+1];
	if(f[l][deep]>f[r-(1<<deep)+1][deep]){
		return id[r-(1<<deep)+1][deep];
	}else{
		return id[l][deep];
	}
}
int dis(int a,int b){
	int lc=lca(a,b);
	return 2*dep[lc]-dep[a]-dep[b];
}
pair<int,int> tr[800050];
void add(pair<int,int> &p,int c){
	int a=p.first,b=p.second;
	if(a==-1||b==-1)return;
	int ab=dis(a,b),ac=dis(a,c),bc=dis(b,c);
	if(ac+bc==ab)return;
	if(ac+ab==bc){
		p.first=c;return;
	}
	if(bc+ab==ac){
		p.second=c;return;
	}
	p.first=-1,p.second=-1;
	return;
}
void up(int p){
	int a=tr[2*p].first,b=tr[2*p].second,c=tr[2*p+1].first,d=tr[2*p+1].second;
	if(a==-1||b==-1||c==-1||d==-1){
		tr[p]=make_pair(-1,-1);
		return;
	}
	tr[p]=make_pair(a,b);
	add(tr[p],c);
	add(tr[p],d);
}
void build(int p,int l,int r){
	if(l==r){
		tr[p]=make_pair(pos[l-1],pos[l-1]);
		return;
	}
	int mid=l+r>>1;
	build(2*p,l,mid);
	build(2*p+1,mid+1,r);
	up(p);
}
void update(int p,int l,int r,int x,int w){
	if(l==r){
		tr[p]=make_pair(w,w);
		return;
	}
	int mid=l+r>>1;
	if(x<=mid)update(2*p,l,mid,x,w);
	else update(2*p+1,mid+1,r,x,w);
	up(p);
}
pair<int,int> res;
int query(int p,int l,int r){
	if(l==r){
		add(res,pos[l-1]);
		if(res.first!=-1)return l;
		else return l-1;
	}
	int mid=l+r>>1;
	if(tr[2*p].first!=-1){
		if(res.first==0){
			res=tr[2*p];
			return query(2*p+1,mid+1,r);
		}else {
			pair<int,int> tmp=res;
			add(tmp,tr[2*p].first);
			add(tmp,tr[2*p].second);
			if(tmp.first!=-1){
				res=tmp;
				return query(2*p+1,mid+1,r);
			}else return query(2*p,l,mid);
		}
	}else return query(2*p,l,mid);
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		int a;scanf("%d",&a);
		pos[a]=i;
		val[i]=a;
	}
	for(int i=1;i<n;i++){
		int a;scanf("%d",&a);
		v[a].push_back(i+1);
	}
	dfs(1,0);
	Loginit(len);
	rmq();
	scanf("%d",&m);
	build(1,1,n);
	while(m--){
		int op;scanf("%d",&op);
		int l,r;
		res=make_pair(0,0);
		if(op==1){
			scanf("%d%d",&l,&r);
			update(1,1,n,val[l]+1,r);
			update(1,1,n,val[r]+1,l);
			swap(val[l],val[r]);
			swap(pos[val[l]],pos[val[r]]);
		}else printf("%d\n",query(1,1,n));
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值