[CTSC2008] 网络管理
一、题目
二、解法
一个比较显然的做法的树剖维护树套树,时间复杂度 \(O(n\log^3n)\)
现在讲一下 \(O(n\log^2 n)\) 的做法,首先考虑不带修改怎么做,每个点维护到根的权值线段树,然后直接拿 \(u,v,lca,fa[lca]\) 这四个根在线段树上二分即可,用主席树优化一下就可以做到 \(O(n\log n)\)
但是这道题是单点修改的,主席树带修之后变成了树套树,我们可以在外层套一个以 \(dfn\) 序为下标的树状数组,修改就修改子树内所有的权值线段树,也就是对于一段连续的区间进行修改。那么就维护一棵元素为权值线段树的差分树状数组,就是区间修改单点查询的经典问题了,询问的时候就带着 \(O(\log n)\) 个根去线段树二分即可,时间复杂度 \(O(n\log^2 n)\)
#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
const int M = 80005;
const int up = 1e8;
#define pii pair<int,int>
#define make make_pair
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,q,tot,Ind,f[M],a[M],fa[M][20],dfn[M],dfo[M],dep[M];
int cnt,rt[M],sum[200*M],ls[200*M],rs[200*M];
vector<pii> v;//询问用的根集合
//rt[i]表示树状数组节点对应线段树的根
//sum[i]表示线段树的子树和(出现次数)
struct edge
{
int v,next;
edge(int V=0,int N=0) : v(V) , next(N) {}
}e[2*M];
void dfs(int u)//预处理,算dfs序
{
dfn[u]=++Ind;
dep[u]=dep[fa[u][0]]+1;
for(int i=1;i<20;i++)
fa[u][i]=fa[fa[u][i-1]][i-1];
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v;
if(v==fa[u][0]) continue;
fa[v][0]=u;
dfs(v);
}
dfo[u]=Ind;
}
int lca(int u,int v)
{
if(dep[u]<dep[v]) swap(u,v);
for(int i=19;i>=0;i--)
if(dep[fa[u][i]]>=dep[v])
u=fa[u][i];
if(u==v) return u;
for(int i=19;i>=0;i--)
if(fa[u][i]^fa[v][i])
u=fa[u][i],v=fa[v][i];
return fa[u][0];
}
int lowbit(int x)
{
return x&(-x);
}
void ins(int &x,int l,int r,int y,int f)//权值线段树的插入
{
if(!x) x=++cnt;
sum[x]+=f;
if(l==r) return ;
int mid=(l+r)>>1;
if(mid>=y) ins(ls[x],l,mid,y,f);
else ins(rs[x],mid+1,r,y,f);
}
void upd(int x,int y,int f)
//修改dfn序为x位置的差分值,插入权值y
{
while(x<=n)
{
ins(rt[x],1,up,y,f);
x+=lowbit(x);
}
}
void ask(int x,int f)
//其实这里是取出差分前缀x的所有根,并且带上正负标记
{
while(x>0)
{
v.push_back(make(rt[x],f));
x-=lowbit(x);
}
}
int qry(int l,int r,int k)//值域区间[l,r]的第k大
{
int t=0;
for(int i=0;i<v.size();i++)
t+=v[i].second*sum[rs[v[i].first]];
if(l==r) return l;
int mid=(l+r)>>1;
if(t>=k)//分到右边
{
for(int i=0;i<v.size();i++)
v[i].first=rs[v[i].first];
return qry(mid+1,r,k);
}
k-=t;//减掉之后分左边
for(int i=0;i<v.size();i++)
v[i].first=ls[v[i].first];
return qry(l,mid,k);
}
signed main()
{
n=read();q=read();
for(int i=1;i<=n;i++)
a[i]=read();
for(int i=1;i<n;i++)
{
int u=read(),v=read();
e[++tot]=edge(v,f[u]),f[u]=tot;
e[++tot]=edge(u,f[v]),f[v]=tot;
}
dfs(1);
for(int i=1;i<=n;i++)
{
upd(dfn[i],a[i],1);
upd(dfo[i]+1,a[i],-1);
}
while(q--)
{
int k=read(),x=read(),y=read();
if(k==0)
{
upd(dfn[x],a[x],-1);
upd(dfo[x]+1,a[x],1);
a[x]=y;
upd(dfn[x],a[x],1);
upd(dfo[x]+1,a[x],-1);
}
else
{
int t=lca(x,y);v.clear();
ask(dfn[x],1);
ask(dfn[y],1);
ask(dfn[t],-1);
ask(dfn[fa[t][0]],-1);
t=0;
for(int i=0;i<v.size();i++)
t+=sum[v[i].first]*v[i].second;
if(t<k) puts("invalid request!");
else printf("%d\n",qry(1,up,k));
}
}
}