Codeforces757G Can Bash Save the Day?【可持久化点分树】

题目描述:

给出 n n n个点的树和排列 p i p_i pi,有边权,Q次操作:

  • 1   l   r   x 1~l~r~x 1 l r x:求 ∑ i = l r d i s ( p i , x ) \sum_{i=l}^rdis(p_i,x) i=lrdis(pi,x)
  • 2   x 2~x 2 x s w a p ( p x , p x + 1 ) swap(p_x,p_{x+1}) swap(px,px+1)

n , Q ≤ 200000 n,Q\le200000 n,Q200000

题目分析:

首先肯定需要一个可持久化(前缀和)数据结构来维护 ∑ i = 1 r d i s ( p i , x ) \sum_{i=1}^rdis(p_i,x) i=1rdis(pi,x)
发现修改只需要修改 x x x对应的那棵树,令 r t [ x ] = r t [ x − 1 ] rt[x]=rt[x-1] rt[x]=rt[x1],再在其中插入 p x + 1 p_{x+1} px+1即可。

如果用主席树的话要套上一个树链剖分,空间是 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)的,需要卡一卡,比如说当点数达到三千万的时候清空全部重构。

然而有可持久化点分树的做法,时空复杂度都是 O ( n l o g n ) O(nlogn) O(nlogn)

题目相当于求一个点集到 x x x的距离之和,如果用平常的点分树就是在点分中心 u u u维护子树内到它的距离和,子树内点的个数,以及子树到它点分树上的父亲的距离和,查询就很easy。

点分树的可持久化相当于每次在原来的基础上插入一条链(跟可持久化线段树类似),但是问题是每次需要复制点分树上的儿子,所以我们要用多叉树转二叉树使得每个点的度数 ≤ 3 \le3 3(转换的方法看这里
并且我们需要知道 x x x在当前点点分树上 u u u的哪个儿子中,可以用vector或者数组存下每一层 x x x在第几个儿子子树中。

(我不会写点分树了555。。调了几个小时)
Code:

#include<bits/stdc++.h>
#define maxn 400005
#define maxp maxn*20
#define LL long long
using namespace std;
char cb[1<<20],*cs,*ct;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<20,stdin),cs==ct)?0:*cs++)
inline void read(int &a){
	char c;while(!isdigit(c=getc()));
	for(a=c-'0';isdigit(c=getc());a=a*10+c-'0');
}
int n,m,N,a[maxn],rt[maxn],siz[maxn];
int fir[maxn],nxt[maxn<<1],to[maxn<<1],w[maxn<<1],tot;
vector<pair<int,int> >G[maxn];
inline void line(int x,int y,int z){nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y,w[tot]=z;}
void dfs0(int u,int ff){
	for(int i=0,v,p=u;i<G[u].size();i++) if((v=G[u][i].first)!=ff){
		int w=G[u][i].second;
		if((G[u].size()-bool(ff))>=3&&i) line(p,++N,0),line(N,p,0),line(N,v,w),line(v,N,w),p=N;
		else line(u,v,w),line(v,u,w);
		dfs0(v,u);
	}
}
LL dis[20][maxn],sum[maxp],cnt[maxp],sf[maxp];
int id[maxp],dep[maxn],ID[20][maxn],ch[maxp][3],sz;
bool vis[maxn];
void getrt(int u,int ff,int tsz,int &g){
	siz[u]=1; bool flg=1;
	for(int i=fir[u],v;i;i=nxt[i]) if(!vis[v=to[i]]&&v!=ff)
		getrt(v,u,tsz,g),siz[u]+=siz[v],flg&=siz[v]<<1<=tsz;
	if(flg&&(tsz-siz[u])<<1<=tsz) g=u;
}
void dfs(int u,int ff,int d){
	for(int i=fir[u],v;i;i=nxt[i]) if(!vis[v=to[i]]&&v!=ff) dis[d][v]=dis[d][u]+w[i],dfs(v,u,d);
}
void build(int u,int tsz){
	vis[u]=1,id[u]=u;
	int d=dep[u]; dfs(u,0,d);
	for(int i=fir[u],v,t=0,tmp;i;i=nxt[i]) if(!vis[v=to[i]]){
		getrt(v,0,tmp=siz[v]<=siz[u]?siz[v]:tsz-siz[u],v);
		dep[v]=d+1,ch[u][t]=v,ID[d][v]=t++;
		for(int j=0;j<d;j++) ID[j][v]=ID[j][u];
		build(v,tmp);
	}
}
void ins(int &u,int x){
	id[++sz]=id[u],sum[sz]=sum[u],cnt[sz]=cnt[u],sf[sz]=sf[u];
	memcpy(ch[sz],ch[u],sizeof ch[u]),u=sz;
	int d=dep[id[u]];
	sum[u]+=dis[d][x],cnt[u]++;
	if(d) sf[u]+=dis[d-1][x];
	if(id[u]!=x) ins(ch[u][ID[d][x]],x);
}
LL qry(int u,int x){
	if(!u) return 0;
	LL res=sum[u]+cnt[u]*dis[0][x];
	for(int i=1,v;i<=dep[x];i++,u=v) v=ch[u][ID[i-1][x]],res+=sum[v]+cnt[v]*dis[i][x]-(sf[v]+cnt[v]*dis[i-1][x]);
	return res;
}
int main()
{
	freopen("1.in","r",stdin);
	int op,l,r,x,y,z; LL ans=0;
	read(n),read(m),N=n;
	for(int i=1;i<=n;i++) read(a[i]);
	for(int i=1;i<n;i++) read(x),read(y),read(z),G[x].push_back(make_pair(y,z)),G[y].push_back(make_pair(x,z));
	dfs0(1,0);
	build((getrt(1,0,N,rt[0]),rt[0]),N);
	sz=N;
	for(int i=1;i<=n;i++) 
		ins(rt[i]=rt[i-1],a[i]);
	while(m--){
		read(op);
		if(op==1) read(l),read(r),read(x),l^=ans,r^=ans,x^=ans,printf("%lld\n",ans=qry(rt[r],x)-qry(rt[l-1],x)),ans%=1<<30;
		else read(x),x^=ans,swap(a[x],a[x+1]),ins(rt[x]=rt[x-1],a[x]);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值