bzoj1146/codevs1490 网络管理 树状数组套主席树

题目分析

首先我们发现,这个题目长得有点像带修改区间第k大,只不过被放在了树上。那么就考虑树状数组套主席树的经典解法吧。
我们设要求x到y的路径上的第k大,o为x和y的lca,那么我们可以利用前缀和,前缀和(x)+前缀和(y)-前缀和(o)-前缀和(o的父亲)就是我们要求的。
现在看修改,修改了x之后,以x为根的子树都会被修改的。这让我们想到了dfs序,可以在入序处将该权值数量+1,出序+1处将该权值-1,即可快速维护前缀和。
由以上零散的思路,我们可以得到一个算法:
1.离线读入所有操作,哈希涉及到的节点值
2.遍历子树,预处理倍增,并求出dfs序
3.用树状数组套主席树维护
4.对于每一个节点,我们在其dfs入序处将该权值数量+1,在其dfs出序+1处将该权值数量-1
5.对于修改操作,我们消除上一个权值的影响,添加这一个权值的影响
6.对于询问操作,我们将与x和y有关的查询值保存一份,再将与o和o的父亲有关的查询值保存一份,进行查询。(这句话没讲明白,其实就是和普通的树状数组套主席树的查询差不多)

注意事项

1.请注意要求的是第k大值
2.请注意开大主席树空间
3.请注意开大哈希数组空间
总之因为以上三点,本蒟蒻的代码调了很久很久,并渐进等于神犇的代码了…

代码

#include<bits/stdc++.h>
using namespace std;
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
const int N=80005,M=30005;
int n,q,js,tt=1,tot,now,cur;
int h[N],to[N<<1],ne[N<<1],rt[N],Q[2][50];
int a[N],b[N<<1],K[M],A[M],B[M],in[N],le[N],pre[N][18],dep[N];
struct node{int ls,rs,sum;}tr[N*120];
void adde(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}
int getn(int x) {return lower_bound(b+1,b+1+tt,x)-b;}
int lowbit(int x) {return x&(-x);}
int lca(int x,int y) {
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=17;i>=0;--i) if(dep[pre[x][i]]>=dep[y]) x=pre[x][i];
	if(x==y) return x;
	for(int i=17;i>=0;--i)
		if(pre[x][i]!=pre[y][i]) x=pre[x][i],y=pre[y][i];
	return pre[x][0];
}
int ask(int l,int r,int kth) {
	if(l==r) return l;
	int s0=0,s1=0,mid=(l+r)>>1;
	for(int i=1;i<=Q[0][0];++i) s0+=tr[tr[Q[0][i]].ls].sum;
	for(int i=1;i<=Q[1][0];++i) s1+=tr[tr[Q[1][i]].ls].sum;
	if(s0-s1>=kth) {
		for(int i=1;i<=Q[0][0];++i) Q[0][i]=tr[Q[0][i]].ls;
		for(int i=1;i<=Q[1][0];++i) Q[1][i]=tr[Q[1][i]].ls;
		return ask(l,mid,kth);
	}
	else {
		for(int i=1;i<=Q[0][0];++i) Q[0][i]=tr[Q[0][i]].rs;
		for(int i=1;i<=Q[1][0];++i) Q[1][i]=tr[Q[1][i]].rs;
		return ask(mid+1,r,kth-(s0-s1));
	}
}
int query(int x,int y,int kth) {
	int o=lca(x,y),js=dep[x]+dep[y]-dep[o]*2+1;
	if(js<kth) return -1;
	Q[0][0]=Q[1][0]=0;//0:add 1:sub
	for(int i=in[x];i;i-=lowbit(i)) Q[0][++Q[0][0]]=rt[i];
	for(int i=in[y];i;i-=lowbit(i)) Q[0][++Q[0][0]]=rt[i];
	for(int i=in[o];i;i-=lowbit(i)) Q[1][++Q[1][0]]=rt[i];
	for(int i=in[pre[o][0]];i;i-=lowbit(i)) Q[1][++Q[1][0]]=rt[i];
	return b[ask(1,tt,js-kth+1)];
}
void ins(int l,int r,int &x,int num,int w) {
	if(!x) x=++cur; tr[x].sum+=w;
	if(l==r) return;
	int mid=(l+r)>>1;
	if(num<=mid) ins(l,mid,tr[x].ls,num,w);
	else ins(mid+1,r,tr[x].rs,num,w);
}
void add(int x,int num,int w)
{while(x<=n) ins(1,tt,rt[x],num,w),x+=lowbit(x);}
void dfs(int x,int las) {
	in[x]=++now,pre[x][0]=las,dep[x]=dep[las]+1,add(now,a[x],1);
	for(int i=1;i<=17;++i) pre[x][i]=pre[pre[x][i-1]][i-1];
	for(int i=h[x];i;i=ne[i]) if(to[i]!=las) dfs(to[i],x);
	le[x]=now,add(now+1,a[x],-1);
}
int main()
{
	int x,y,re;
	n=read(),q=read();
	for(int i=1;i<=n;++i) a[i]=b[++js]=read();
	for(int i=1;i<n;++i) x=read(),y=read(),adde(x,y),adde(y,x);
	for(int i=1;i<=q;++i) {
		K[i]=read(),A[i]=read(),B[i]=read();
		if(!K[i]) b[++js]=B[i];
	}
	sort(b+1,b+1+js);for(int i=2;i<=js;++i) if(b[i]!=b[tt]) b[++tt]=b[i];
	for(int i=1;i<=n;++i) a[i]=getn(a[i]);
	dfs(1,0);
	for(int i=1;i<=q;++i) {
		x=A[i],y=B[i];
		if(!K[i]) {
			B[i]=getn(B[i]),add(in[x],a[x],-1),add(le[x]+1,a[x],1);
			add(in[x],B[i],1),add(le[x]+1,B[i],-1),a[x]=B[i];
		}
		else {
			re=query(A[i],B[i],K[i]);
			if(re>0) printf("%d\n",re);
			else puts("invalid request!");
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值