CF1464F My Beautiful Madness 题解
CF1464F My Beautiful Madness
Meaning
定义树上一条路径的
Solution
我们难以判断路径关系,欲求一种简单的等价的容易判断的表示法,可以抓住树上某一个关键点,如果有解,那它必然处于所有路径的交集,而这个关键节点则是树上最深的 LCA 的 画图感性理解一下。
假设我们加了
求出了关键节点后,我们又应该如何判断呢,我们可以子树内外分别考虑,对于子树内的路径满足,因为最深的 LCA 都能到达;考虑子树外的路径,因为我们选取的关键节点,即是在满足子树内的限制尽量往外走的点,所以等效代替,现在的判定变为了计算关键节点与关键节点子树外的 LCA 的距离的最小值与
同时我们令关键节点为 脑洞画图理解一波。
再次假设我们加了
Hint
经过上面的讲解,我们可以来理一理我们需要求解的东西和需要用到的算法了:
- 处理出树的
dfn 序,然后倍增求 LCA。 - 用
multiset记录路径和深度,每次取深度最大值。 - 树状数组维护树上差分,统计路径条数。
- 线段树维护树的直径。
- 判断所有路径是否经过
v 的子树,以及计算子树直径与2d 的关系。
下面就是代码了
AC Code
#include<bits/stdc++.h>
using namespace std;
int n,m,dfn,inf=0x3f3f3f3f,dep[200001],st[200001][21],ls[200001],rs[200001],cnt[200001];
vector <int> G[200001];
multiset <pair<int,int> > s;//记录路径及其lca的深度
struct spot{
int x,y,val;
};
void get(int rt,int fa){
ls[rt]=++dfn;
dep[rt]=dep[fa]+1;
st[rt][0]=fa;
for(int i=1;i<=20;i++) st[rt][i]=st[st[rt][i-1]][i-1];
for(int i=0;i<G[rt].size();i++){
int to=G[rt][i];
if(fa!=to) get(to,rt);
}
rs[rt]=dfn;
}
int lca(int u,int v){
if(dep[u]<dep[v]) swap(u,v);
for(int i=20;i>=0;i--){
if(dep[st[u][i]]>=dep[v]) u=st[u][i];
}
if(u==v) return u;
for(int i=20;i>=0;i--){
if(st[u][i]!=st[v][i]){
u=st[u][i];
v=st[v][i];
}
}
return st[u][0];
}
int dist(int u,int v){
if(u==-1||v==-1) return -inf;
int fa=lca(u,v);
return dep[u]+dep[v]-2*dep[fa];
}
int find(int x,int k){
for(int i=20;i>=0;i--){
if((1<<i)<=k&&st[x][i]){
k-=(1<<i);
x=st[x][i];
}
else if((1<<i)<=k&&!st[x][i]) return 1;
}
return x;
}
spot merge(spot p,spot q){
if(p.x==-1) return q;
if(q.x==-1) return p;
spot res=q;
if(p.val>q.val) res=p;
int a=dist(p.x,q.x),b=dist(p.x,q.y),c=dist(p.y,q.x),d=dist(p.y,q.y);
if(res.val<a) res=spot{p.x,q.x,a};
if(res.val<b) res=spot{p.x,q.y,b};
if(res.val<c) res=spot{p.y,q.x,c};
if(res.val<d) res=spot{p.y,q.y,d};
return res;
}
struct SegmentTree{
spot node[800001];
void build(int rt,int l,int r){
node[rt]=spot{-1,-1,-inf};
if(l==r) return;
int mid=(l+r)/2;
build(rt*2,l,mid);
build(rt*2+1,mid+1,r);
}
spot query(int rt,int l,int r,int L,int R){
if(L<=l&&r<=R) return node[rt];
int mid=(l+r)/2;
if(R<=mid) return query(rt*2,l,mid,L,R);
if(L>=mid+1) return query(rt*2+1,mid+1,r,L,R);
return merge(query(rt*2,l,mid,L,R),query(rt*2+1,mid+1,r,L,R));
}
void updata(int rt,int l,int r,int id,int x,int pos){
if(l==r&&l==id){
if(pos==1) node[rt]=spot{x,x,0};
else node[rt]=spot{-1,-1,-inf};
return;
}
int mid=(l+r)/2;
if(id<=mid) updata(rt*2,l,mid,id,x,pos);
else updata(rt*2+1,mid+1,r,id,x,pos);
node[rt]=merge(node[rt*2],node[rt*2+1]);
}
}t1;//维护树的直径
struct TreeArray{
int tree[800001];
void add(int x,int n,int y){
for(int i=x;i<=n;i+=i&-i) tree[i]+=y;
}
int ask(int x){
int res=0;
for(int i=x;i>=1;i-=i&-i) res+=tree[i];
return res;
}
}t2;//维护树上差分,记录路径条数
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++){
int x,y;
scanf("%d%d",&x,&y);
G[x].push_back(y);
G[y].push_back(x);
}
get(1,0);
t1.build(1,1,n);
while(m--){
int pos;
scanf("%d",&pos);
if(pos==1){
int x,y;
scanf("%d%d",&x,&y);
int z=lca(x,y);
t2.add(ls[x],n,1);
t2.add(ls[y],n,1);
t2.add(ls[z],n,-1);
s.insert(make_pair(-dep[z],z));
cnt[z]++;
if(cnt[z]==1) t1.updata(1,1,n,ls[z],z,1);
}
else if(pos==2){
int x,y;
scanf("%d%d",&x,&y);
int z=lca(x,y);
t2.add(ls[x],n,-1);
t2.add(ls[y],n,-1);
t2.add(ls[z],n,1);
s.erase(s.lower_bound(make_pair(-dep[z],z)));
cnt[z]--;
if(cnt[z]==0) t1.updata(1,1,n,ls[z],z,0);
}
else{//所有路径一定要么穿过它,要么在它的子树内
int d;
scanf("%d",&d);
int x=find((*s.begin()).second,d),y=find(x,d);
if(t2.ask(rs[y])-t2.ask(ls[y]-1)!=s.size()){//不是所有路径与它的子树有交集
printf("No\n");
continue;
}
spot res=t1.query(1,1,n,ls[y],rs[y]);//区间最长链(子树直径)
if(dist(res.x,x)<=d&&dist(res.y,x)<=d) printf("Yes\n");
else printf("No\n");
}
}
return 0;
}