[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)); 
		}
	}
}
posted @ 2021-03-17 11:02  C202044zxy  阅读(78)  评论(0编辑  收藏  举报