用到的知识
线段树/树状数组+树链剖分+整体二分
没学过的建议先去学了再回来做这道题
解题思路
题目要求的是不经过x的所有路径中最大的路径,那么我们可以考虑使用二分:
对于每条v大于mid的路径,将路径上的点+1(路径上的点对应线段树的点,这部分使用树链剖分+线段树/树状数组实现),并统计路径总数,然后查询x点的值,如果x点的值与路径总数相等,即证明所有大于mid的路径都经过x,此时需要减小mid,重复操作,否则,则存在不经过x的路径,增大mid
我们发现,这个操作可以同时针对所有询问,于是,使用整体二分
复杂度O(m logm logn logn) 为什么是三个log? logm是对离散化后的答案区间二分(一共m种重要度),第一层logn是树链剖分路径修改,第二层logn 是线段树/树状数组查询(一共n个点)
用到的算法
树状数组(可以用线段树)
树状数组用于区间+1和-1(请求结束可视为区间-1),查询x点的值
我们把树状数组视为差分数组,两个单点更新即为区间更新,查询前缀和即为查询单点的值
代码实现:
//树状数组 #define lowbit(i) i&-i int tree[mx];//树状数组的差分数组 void change_point(int i,int k){//更新差分数组 while(i<=n){ tree[i]+=k; i+=lowbit(i); } } int query(int i){//单点查询 int ans=0; while(i>0){ ans+=tree[i]; i-=lowbit(i); } return ans; } void change(int l,int r,int k){//区间更新 change_point(l,k);change_point(r+1,-k); }
树链剖分
我们使用树链剖分来把一颗树变成一个区间,并使得在同一条重链上的点的编号连续
代码实现:
//树剖 int head[mx]; int num; struct Edge{ int v,next; }edge[mx<<1]; void addedge(int u,int v){//加一条单向边 edge[++num].v=v; edge[num].next=head[u]; head[u]=num; } void add_edge(int u,int v){//加双向边 addedge(u,v); addedge(v,u); } int dfn[mx],deep[mx];//每个点的dfs序,深度 int fa[mx],size[mx],son[mx];//每个点的父亲,子树大小,重儿子 void dfs1(int u,int f){ size[u]=1; int max_size=0; for(int i=head[u];i;i=edge[i].next){ int v=edge[i].v; if(v==f)continue; fa[v]=u; deep[v]=deep[u]+1; dfs1(v,u); size[u]+=size[v]; if(max_size<size[v]){ max_size=size[v]; son[u]=v; } } } int T;//时间戳 int top[mx];//重链顶端 void dfs2(int u,int tp){ dfn[u]=++T; top[u]=tp; if(son[u])dfs2(son[u],tp); for(int i=head[u];i;i=edge[i].next){ int v=edge[i].v; if(fa[u]!=v&&son[u]!=v)dfs2(v,v); } } void change_path(int u,int v,int k){//从u到v的路径上的点全部+k while(top[u]!=top[v]){ if(deep[top[u]]>deep[top[v]])swap(u,v); change(dfn[top[v]],dfn[v],k); v=fa[top[v]]; } if(deep[u]<deep[v])swap(u,v); change(dfn[v],dfn[u],k); }
整体二分
前面已经讲的比较详细了,这里直接放代码:
//整体二分 struct Query{ int type;//type==0:(a,b,t,v) type==1:del(t) type==2:x was broken int a,b,t,v; int k;//type==0:k=1 type==1:k=-1 int x; }q[mx],ql[mx],qr[mx]; bool Cmp(Query x,Query y){//按t升序 return x.t<y.t; } int cnt_a;//一共多少个v int a[mx];//原序列 int b[mx];//离散化序列 int O;//离散化后共多少个数 void discretization(){//离散化 sort(a+1,a+1+cnt_a); b[++O]=a[1]; for(int i=2;i<=cnt_a;++i)if(a[i]!=a[i-1])b[++O]=a[i]; } int ans[mx];//每个询问的答案 void solve(int l,int r,int L,int R){//[l,r]为当前答案所在区间,[L,R]为当前处理问题区间 if(l==r){ for(int i=L;i<=R;++i)if(q[i].type==2)ans[q[i].t]=l; return; } int cnt1=0,cnt2=0,mid=(l+r)>>1;//左右区间的操作数 int s=0;//当前共有多少个请求 for(int i=L;i<=R;++i){ if(q[i].type==2){ int sum=query(dfn[q[i].x]);//v比mid大并且经过x的请求有多少个 if(sum==s)ql[++cnt1]=q[i];//比mid大的请求全部被断了,往小的找 else qr[++cnt2]=q[i];//有没被断的请求 } else{ if(q[i].v>b[mid]){ change_path(q[i].a,q[i].b,q[i].k); s+=q[i].k; qr[++cnt2]=q[i]; } else ql[++cnt1]=q[i]; } } for(int i=1;i<=cnt2;++i)if(qr[i].type!=2)change_path(qr[i].a,qr[i].b,-qr[i].k);//清空树状数组 for(int i=1;i<=cnt1;++i)q[L+i-1]=ql[i]; for(int i=1;i<=cnt2;++i)q[L+cnt1+i-1]=qr[i]; solve(l,mid,L,L+cnt1-1); solve(mid+1,r,cnt1+L,R); }
完整代码
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> using namespace std; inline int Read(){ int x=0; char c=getchar(); while(c>'9'||c<'0')c=getchar(); while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar(); return x; } const int mx=201010; int n,m; //树状数组 #define lowbit(i) i&-i int tree[mx];//树状数组的差分数组 void change_point(int i,int k){//更新差分数组 while(i<=n){ tree[i]+=k; i+=lowbit(i); } } int query(int i){//单点查询 int ans=0; while(i>0){ ans+=tree[i]; i-=lowbit(i); } return ans; } void change(int l,int r,int k){//区间更新 change_point(l,k);change_point(r+1,-k); } //树剖 int head[mx]; int num; struct Edge{ int v,next; }edge[mx<<1]; void addedge(int u,int v){//加一条单向边 edge[++num].v=v; edge[num].next=head[u]; head[u]=num; } void add_edge(int u,int v){//加双向边 addedge(u,v); addedge(v,u); } int dfn[mx],deep[mx];//每个点的dfs序,深度 int fa[mx],size[mx],son[mx];//每个点的父亲,子树大小,重儿子 void dfs1(int u,int f){ size[u]=1; int max_size=0; for(int i=head[u];i;i=edge[i].next){ int v=edge[i].v; if(v==f)continue; fa[v]=u; deep[v]=deep[u]+1; dfs1(v,u); size[u]+=size[v]; if(max_size<size[v]){ max_size=size[v]; son[u]=v; } } } int T;//时间戳 int top[mx];//重链顶端 void dfs2(int u,int tp){ dfn[u]=++T; top[u]=tp; if(son[u])dfs2(son[u],tp); for(int i=head[u];i;i=edge[i].next){ int v=edge[i].v; if(fa[u]!=v&&son[u]!=v)dfs2(v,v); } } void change_path(int u,int v,int k){//从u到v的路径上的点全部+k while(top[u]!=top[v]){ if(deep[top[u]]>deep[top[v]])swap(u,v); change(dfn[top[v]],dfn[v],k); v=fa[top[v]]; } if(deep[u]<deep[v])swap(u,v); change(dfn[v],dfn[u],k); } //整体二分 struct Query{ int type;//type==0:(a,b,t,v) type==1:del(t) type==2:x was broken int a,b,t,v; int k;//type==0:k=1 type==1:k=-1 int x; }q[mx],ql[mx],qr[mx]; bool Cmp(Query x,Query y){//按t升序 return x.t<y.t; } int cnt_a;//一共多少个v int a[mx];//原序列 int b[mx];//离散化序列 int O;//离散化后共多少个数 void discretization(){//离散化 sort(a+1,a+1+cnt_a); b[++O]=a[1]; for(int i=2;i<=cnt_a;++i)if(a[i]!=a[i-1])b[++O]=a[i]; } int ans[mx];//每个询问的答案 void solve(int l,int r,int L,int R){//[l,r]为当前答案所在区间,[L,R]为当前处理问题区间 if(l==r){ for(int i=L;i<=R;++i)if(q[i].type==2)ans[q[i].t]=l; return; } int cnt1=0,cnt2=0,mid=(l+r)>>1;//左右区间的操作数 int s=0;//当前共有多少个请求 for(int i=L;i<=R;++i){ if(q[i].type==2){ int sum=query(dfn[q[i].x]);//v比mid大并且经过x的请求有多少个 if(sum==s)ql[++cnt1]=q[i];//比mid大的请求全部被断了,往小的找 else qr[++cnt2]=q[i];//有没被断的请求 } else{ if(q[i].v>b[mid]){ change_path(q[i].a,q[i].b,q[i].k); s+=q[i].k; qr[++cnt2]=q[i]; } else ql[++cnt1]=q[i]; } } for(int i=1;i<=cnt2;++i)if(qr[i].type!=2)change_path(qr[i].a,qr[i].b,-qr[i].k);//清空树状数组 for(int i=1;i<=cnt1;++i)q[L+i-1]=ql[i]; for(int i=1;i<=cnt2;++i)q[L+cnt1+i-1]=qr[i]; solve(l,mid,L,L+cnt1-1); solve(mid+1,r,cnt1+L,R); } int main(){ n=Read(),m=Read(); for(int i=1;i<n;++i){ int u=Read(),v=Read(); add_edge(u,v); } dfs1(1,1); dfs2(1,1); for(int i=1;i<=m;++i){ q[i].type=Read(); if(q[i].type==0){ q[i].a=Read(); q[i].b=Read(); a[++cnt_a]=q[i].v=Read(); q[i].k=1; } if(q[i].type==1){ int t=Read(); q[i]=q[t]; q[i].k=-1; } if(q[i].type==2){ q[i].x=Read(); } q[i].t=i; } discretization(); solve(0,O,1,m); sort(q+1,q+1+m,Cmp); for(int i=1;i<=m;++i)if(q[i].type==2)printf("%d\n",ans[q[i].t]!=0?b[ans[q[i].t]]:-1); return 0; }
解释一下倒数第二行:如果ans==0,则证明对于任何mid>0,所有路径都会经过x,所以按题目要求,输出-1