题目链接
https://vjudge.net/problem/CodeForces-1062E
题意
给出一棵树,编号1-n,每次询问L-R区间点中,删掉一个点后LCA的最小深度(根为0),输出删去的点和深度
思路
训练的时候只想到了最暴力的 O ( n ² q l o g ( l o g n ) ) O(n²qlog(logn)) O(n²qlog(logn))的算法。
仔细观察,发现一组点的LCA就是dfn最小和最大的点的LCA,那么如果删点,肯定是删去这两个点其中之一。于是我们用线段树维护区间dfn最大最小值,对于每个询问我们处理出区间内最大dfn,次大dfn,最小dfn,次小dfn对应的节点(需要做dfs时建立映射),比较LCA(最大,次小),LCA(次大,最小)的深度,分情况输出即可。
复杂度
O
(
q
∗
(
l
o
g
n
+
l
o
g
(
l
o
g
n
)
)
)
O(q*(logn+log(logn)))
O(q∗(logn+log(logn)))
教训/收获
图论部分题很考验直觉/观察。不要自闭,相信自己的实力,毫无头绪和一遍AC很多时候只差一个结论。____一念起,刹那天地宽。
数据结构实在不擅长,调了好久,麻了,线段树这种简单数据结构还是要练一练的
代码
#include<iostream>
#include<string>
#include<queue>
#include<stack>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define endl "\n"
//#define int long long
using namespace std;
typedef long long ll;
const int maxn=500005;
const int inf=0x3f3f3f3f;
int n,m,k,sq_t;
int head[maxn],cnt,f[maxn][20],dep[maxn];
int dfn[maxn],dfn_to_no[maxn];
struct segtree{
int l,r;
int ma,mi;
} t[maxn<<2];
void build(int root,int l,int r){
t[root].l=l,t[root].r=r;
if(l==r){
t[root].ma=t[root].mi=dfn[l];
return;
}
ll mid=l+r>>1;
build(root<<1,l,mid);
build(root<<1|1,mid+1,r);
t[root].ma=max(t[root<<1].ma,t[root<<1|1].ma);
t[root].mi=min(t[root<<1].mi,t[root<<1|1].mi);
}
ll qmax(ll root,ll l,ll r){
if(l<=t[root].l&&r>=t[root].r){
return t[root].ma;
}
ll mid=t[root].l+t[root].r>>1;
ll ans=-1;
if (l<=mid) ans=max(qmax(root<<1,l,r),ans);
if (r>mid) ans=max(qmax(root<<1|1,l,r),ans);
return ans;
}
ll qmin(ll root,ll l,ll r){
if(l<=t[root].l&&r>=t[root].r){
return t[root].mi;
}
ll mid=t[root].l+t[root].r>>1;
ll ans=inf;
if (l<=mid) ans=min(qmin(root<<1,l,r),ans);
if (r>mid) ans=min(qmin(root<<1|1,l,r),ans);
return ans;
}
struct Edge{
int to,next;
}edge[maxn];
void init(){
memset(head,-1,sizeof head);
cnt=0;
}
void add(int u,int v){
edge[cnt]={v,head[u]};
head[u]=cnt++;
}
queue<int>q;
void LCA_prework(int root){
q.push(root);
dep[root]=1;
while(q.size()){
int x=q.front();q.pop();
for(int i=head[x];~i;i=edge[i].next){
int y=edge[i].to;
if(dep[y]) continue;
dep[y]=dep[x]+1;
f[y][0]=x;
for(int j=1;j<=sq_t;j++){
f[y][j]=f[f[y][j-1]][j-1];
}
q.push(y);
}
}
}
int query_LCA(int x,int y){
if(dep[x]>dep[y])
swap(x,y);
for(int i=sq_t;i>=0;i--){
if(dep[f[y][i]]>=dep[x]){
y=f[y][i];
}
}
if(x==y) return x;
for(int i=sq_t;i>=0;i--){
if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
}
return f[x][0];
}
int dfn_cnt;
void dfs(int x){
dfn[x]=++dfn_cnt;dfn_to_no[dfn_cnt]=x;
for(int i=head[x];~i;i=edge[i].next){
if(!dfn[edge[i].to]){
dfs(edge[i].to);
}
}
}
signed main(){
IOS
#ifndef ONLINE_JUDGE
freopen("IO\\in.txt","r",stdin);
freopen("IO\\out.txt","w",stdout);
#endif
cin>>n>>m;
init();
for(int i=2,t;i<=n;i++){
cin>>t;
add(t,i);add(i,t);
}
dfs(1);
sq_t=(int)(log(n)/log(2))+1;
LCA_prework(1);
build(1,1,n);
while(m--){
int x,y;
cin>>x>>y;
int mmax,mmin,cmax,cmin,mmax_no,mmin_no,cmax_no,cmin_no;
mmax=qmax(1,x,y); mmin=qmin(1,x,y);
mmax_no=dfn_to_no[mmax];mmin_no=dfn_to_no[mmin];
cmax=max(qmax(1,x,mmax_no-1),qmax(1,mmax_no+1,y));
cmin=min(qmin(1,x,mmin_no-1),qmin(1,mmin_no+1,y));
cmax_no=dfn_to_no[cmax];
cmin_no=dfn_to_no[cmin];
int ans1=query_LCA(mmax_no,cmin_no);
int ans2=query_LCA(mmin_no,cmax_no);
if(dep[ans1]>dep[ans2]){
cout<<mmin_no<<' '<<dep[ans1]-1<<endl;
}
else{
cout<<mmax_no<<' '<<dep[ans2]-1<<endl;
}
}
}
本文分享了一种高效的算法思路,通过线段树维护区间dfn值,解决Codeforces竞赛中的树删点问题,求LCA最小深度。关键在于观察到删除操作的特性,最终实现O(q*(logn+log(logn)))的时间复杂度,强调了直觉在图论问题中的重要性。

842

被折叠的 条评论
为什么被折叠?



