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 1≤n,q≤2×105,输入保证给出的图是一棵树,且所有结点对应的非负整数 { p i } \{p_i\} {pi} 构成了一个 0 ∼ n − 1 0 \sim n - 1 0∼n−1 的排列。
思路:
首先考虑暴力的做法,想要令
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;
}