[Luogu-P3676]小清新数据结构题-题解

题目地址

本来应该用动态点分治来做的,复杂度是 O ( n l o g n ) O(nlogn) O(nlogn),但是发现树链剖分好像挺好写的XD,于是 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)过了…


题意可以去看原题面。

其实我们数据中询问以1号点为根的,从这里入手,我们先将每个点的子树值的和求出来,记为 s 1 s_1 s1,然后用树剖把它变成序列,然后再记 S 1 ( u ) = ∑ v s 1 ( v ) S_1(u)=\sum_{v}s_1(v) S1(u)=vs1(v),其中 v v v u u u子树中的点,记 S 2 ( u ) = S 1 ( u ) 2 S_2(u)=S_1(u)^2 S2(u)=S1(u)2,我们树剖后用线段树维护这两个值。

考虑序列上维护权值和和和的平方(读起来有点怪XD)。

首先,以一号点为根的答案就是:

∑ i = 1 n s 1 ( i ) 2 \sum_{i=1}^ns_1(i)^2 i=1ns1(i)2

考虑修改操作:

修改一个点 u u u的权值,我们考虑其变化量,假设为 v a l val val,那么会影响到的就是从 1 ∼ u 1\sim u 1u这条路上的所有点的答案,首先加上变化量 v a l val val,答案就变成了:

∑ i = 1 n ( s 1 ( i ) + v a l ) 2 = ∑ i = 1 n s 1 ( i ) 2 + 2 × s 1 ( i ) × v a l + v a l 2 = ∑ i = 1 n s 1 ( i ) 2 + 2 × v a l × ∑ i = 1 n s 1 ( i ) + v a l 2 \sum_{i=1}^n(s_1(i)+val)^2=\sum_{i=1}^ns_1(i)^2+2\times s_1(i)\times val + val^2 \\ =\sum_{i=1}^ns_1(i)^2+2\times val\times \sum_{i=1}^ns_1(i)+val^2 i=1n(s1(i)+val)2=i=1ns1(i)2+2×s1(i)×val+val2=i=1ns1(i)2+2×val×i=1ns1(i)+val2

所以每次增加的就是 v a l 2 val^2 val2 2 × v a l × S 1 ( n ) 2\times val\times S_1(n) 2×val×S1(n),用个 l a z y lazy lazy标记,区间修改即可。


现在考虑根不是 1 1 1的情况:

首先记原来根为 1 1 1的答案为 a n s 1 ans_1 ans1,然后我们考虑,当根变成 r r r时,会发生变化的只有 1 ∼ r 1\sim r 1r这条路上的点的贡献,所以我们记原来每个点的 s 1 ( i ) s_1(i) s1(i) a i a_i ai,换根后的为 b i b_i bi,那么新的答案就为:

∑ i = 1 n s 1 ( i ) 2 − ∑ v ∈ [ 1 ∼ r ] a i 2 + ∑ v ∈ [ 1 ∼ r ] b i 2 = a n s 1 − ∑ v ∈ [ 1 ∼ r ] a i 2 + ∑ v ∈ [ 1 ∼ r ] b i 2 \sum_{i=1}^ns_1(i)^2-\sum_{v\in[1\sim r]}a_i^2+\sum_{v\in[1\sim r]}b_i^2 \\ =ans_1-\sum_{v\in[1\sim r]}a_i^2+\sum_{v\in[1\sim r]}b_i^2 i=1ns1(i)2v[1r]ai2+v[1r]bi2=ans1v[1r]ai2+v[1r]bi2

然后我们看对于改变后的 b i b_i bi不好求,而 a i a_i ai开始已经预处理出来了,所以我们考虑 b i b_i bi a i a_i ai的关系:

可以发现在 1 ∼ r 1\sim r 1r这条路上,假如一个点 u u u的下一个点(从1到r的下一个点)为 v v v,那么就有:

a v + b u = a 1 = b r a_v+b_u=a_1=b_r av+bu=a1=br

所以我们令 b u = a 1 − a v b_u=a_1-a_v bu=a1av,重新编号的话就是 b i = a 1 − a i + 1 b_i=a_1-a_{i+1} bi=a1ai+1,带回原式,假如 1 ∼ r 1\sim r 1r k k k个点:

a n s = a n s 1 − ∑ i = 1 k a i 2 + ∑ i = 1 k − 1 ( a 1 − a i + 1 ) 2 + a 1 2 = a n s 1 − ∑ i = 2 k a i 2 + ∑ i = 2 k ( a 1 2 − 2 ⋅ a 1 ⋅ a i + a i 2 ) = a n s 1 − ∑ i = 2 k a i 2 + ( k − 1 ) a 1 2 − 2 ⋅ a 1 ⋅ ∑ i = 2 k a i + ∑ i = 2 k a i 2 = a n s 1 + ( k − 1 ) a 1 2 − 2 ⋅ a 1 ⋅ ∑ i = 2 k a i = a n s 1 + a 1 ( ( k − 1 ) a 1 − 2 ⋅ ∑ i = 2 k a i ) \begin{aligned} ans =&ans_1-\sum_{i=1}^ka_i^2+\sum_{i=1}^{k-1}(a_1-a_{i+1})^2+a_1^2 \\ =&ans_1-\sum_{i=2}^ka_i^2+\sum_{i=2}^{k}(a_1^2-2\cdot a_1\cdot a_i +a_i^2) \\ =&ans_1-\sum_{i=2}^ka_i^2+(k-1)a_1^2-2\cdot a_1\cdot\sum_{i=2}^{k}a_i+\sum_{i=2}^ka_i^2 \\ =&ans_1+(k-1)a_1^2-2\cdot a_1\cdot\sum_{i=2}^ka_i \\ =&ans_1+a_1\left((k-1)a_1-2\cdot\sum_{i=2}^ka_i\right) \end{aligned} ans=====ans1i=1kai2+i=1k1(a1ai+1)2+a12ans1i=2kai2+i=2k(a122a1ai+ai2)ans1i=2kai2+(k1)a122a1i=2kai+i=2kai2ans1+(k1)a122a1i=2kaians1+a1((k1)a12i=2kai)

a i a_i ai又等于 s 1 ( i ) s_1(i) s1(i),所以带入式子,每次算一下就好啦,复杂度 O ( l o g 2 n ) O(log^2n) O(log2n)

用上面线段树维护的两个 S 1 , S 2 S_1,S_2 S1,S2表示,也就是:

S 2 ( 1 ) + S 1 ( 1 ) ⋅ ( k ⋅ S 1 ( 1 ) − 2 ⋅ ∑ i = 2 k s 1 ( i ) ) S_2(1)+S_1(1)\cdot(k\cdot S_1(1)-2\cdot \sum_{i=2}^ks_1(i)) S2(1)+S1(1)(kS1(1)2i=2ks1(i))

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int M=4e5+10;
int n,m;
int val[M];
struct ss{
	int to,last;
	ss(){}
	ss(int a,int b):to(a),last(b){}
}g[M<<1];
int head[M],cnt;
void add(int a,int b){
	g[++cnt]=ss(b,head[a]);head[a]=cnt;
	g[++cnt]=ss(a,head[b]);head[b]=cnt;
}
int dep[M],f[M],son[M],sze[M],top[M],num[M],rf[M],tim;
ll sum1[M];
ll Sqr(ll a){return a*a;}
void dfs1(int a){
	sze[a]=1;sum1[a]=val[a];
	for(int i=head[a];i;i=g[i].last){
		if(g[i].to==f[a]) continue;
		dep[g[i].to]=dep[a]+1;
		f[g[i].to]=a;
		dfs1(g[i].to);
		sze[a]+=sze[g[i].to];
		sum1[a]+=sum1[g[i].to];
		if(!son[a]||sze[son[a]]<sze[g[i].to])
		son[a]=g[i].to;
	}
}
void dfs2(int a,int b){
	top[a]=b;rf[num[a]=++tim]=a;
	if(!son[a]) return;
	dfs2(son[a],b);
	for(int i=head[a];i;i=g[i].last){
		if(g[i].to==son[a]||g[i].to==f[a]) continue;
		dfs2(g[i].to,g[i].to);
	}
}

ll S1[M<<2],S2[M<<2],lazy[M<<2],All;

void pushup(int o){
	S2[o]=S2[o<<1]+S2[o<<1|1];
	S1[o]=S1[o<<1]+S1[o<<1|1];
}
void pushdown(int o,int l,int r,int mid){
	if(!lazy[o]) return;
	lazy[o<<1]+=lazy[o];
	lazy[o<<1|1]+=lazy[o];
	S2[o<<1]+=Sqr(lazy[o])*(mid-l+1)+2ll*lazy[o]*S1[o<<1];
	S2[o<<1|1]+=Sqr(lazy[o])*(r-mid)+2ll*lazy[o]*S1[o<<1|1];
	S1[o<<1]+=(mid-l+1)*lazy[o];
	S1[o<<1|1]+=(r-mid)*lazy[o];
	lazy[o]=0;
}

void build(int o,int l,int r){
	if(l==r){
		S1[o]=sum1[rf[l]];
		S2[o]=Sqr(S1[o]);
		return;
	}
	int mid=l+r>>1;
	build(o<<1,l,mid);
	build(o<<1|1,mid+1,r);
	pushup(o);
}

void update(int o,int l,int r,int L,int R,ll v){
	if(L<=l&&r<=R){
		S2[o]+=Sqr(v)*(r-l+1)+2ll*v*S1[o];
		S1[o]+=(r-l+1)*v;
		lazy[o]+=v;
		return;
	}
	int mid=l+r>>1;
	pushdown(o,l,r,mid);
	if(L<=mid) update(o<<1,l,mid,L,R,v);
	if(R>mid) update(o<<1|1,mid+1,r,L,R,v);
	pushup(o);
}

void query(int o,int l,int r,int L,int R,ll &a1,ll &a2){
	if(L<=l&&r<=R){
		a1+=S1[o];a2+=S2[o];
		return;
	}
	int mid=l+r>>1;
	pushdown(o,l,r,mid);
	if(L<=mid) query(o<<1,l,mid,L,R,a1,a2);
	if(R>mid) query(o<<1|1,mid+1,r,L,R,a1,a2);
}

ll Query(int a){
	ll ans=0,s1=0,ls=0,k=dep[a];
	query(1,1,n,1,n,ls,ans);
	while(top[a]!=top[1]){
		query(1,1,n,num[top[a]],num[a],s1,ls);
		a=f[top[a]];
	}
	query(1,1,n,num[1]+1,num[a],s1,ls);
	return ans+All*(k*All-2ll*s1);
}
void Update(int a,ll b){
	b-=val[a];All+=b;val[a]+=b;
	while(a){
		update(1,1,n,num[top[a]],num[a],b);
		a=f[top[a]];
	}
}
int a,b,opt;
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;i++){
		scanf("%d%d",&a,&b);
		add(a,b);
	}
	for(int i=1;i<=n;i++)scanf("%d",&val[i]);
	dfs1(1);
	dfs2(1,1);
	build(1,1,n);
	All=sum1[1];
	while(m--){
		scanf("%d",&opt);
		if(opt==1){
			scanf("%d%d",&a,&b);
			Update(a,b);
		}else{
			scanf("%d",&a);
			printf("%lld\n",Query(a));
		}
	}
	return 0;
}
  • 动态点分治

维护原理差不多,放到点分树上而已。

详细讲解先咕咕咕.

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int M=2e5+10;
int n,m;
ll vv[M];
struct ss{
	int to,last;
	ss(){}
	ss(int a,int b):to(a),last(b){}
}g[M<<1];
int head[M],cnt;
void add(int a,int b){
	g[++cnt]=ss(b,head[a]);head[a]=cnt;
	g[++cnt]=ss(a,head[b]);head[b]=cnt;
}

namespace LCA{
	int dep[M],top[M],f[M],son[M],sze[M];
	void dfs1(int a){
		sze[a]=1;
		for(int i=head[a];i;i=g[i].last){
			if(g[i].to==f[a]) continue;
			f[g[i].to]=a;
			dep[g[i].to]=dep[a]+1;
			dfs1(g[i].to);
			sze[a]+=sze[g[i].to];
			if(!son[a]||sze[son[a]]<sze[g[i].to]){
				son[a]=g[i].to;
			}
		}
	}
	void dfs2(int a,int b){
		top[a]=b;
		if(!son[a]) return;
		dfs2(son[a],b);
		for(int i=head[a];i;i=g[i].last){
			if(g[i].to==son[a]||g[i].to==f[a]) continue;
			dfs2(g[i].to,g[i].to);
		}
	}
	int lca(int a,int b){
		while(top[a]!=top[b]){
			if(dep[top[a]]<dep[top[b]])swap(a,b);
			a=f[top[a]];
		}
		if(dep[a]>dep[b])swap(a,b);
		return a;
	}
	int getdep(int a,int b){
		return dep[a]+dep[b]-2*dep[lca(a,b)];
	}
	void init(){
		dfs1(1);
		dfs2(1,1);
	}
} 

int root,sum;
int sze[M],son[M],f[M];
bool vis[M];
void getroot(int a,int fa){
	son[a]=0;sze[a]=1;
	for(int i=head[a];i;i=g[i].last){
		if(vis[g[i].to]||g[i].to==fa) continue;
		getroot(g[i].to,a);
		sze[a]+=sze[g[i].to];
		son[a]=max(son[a],sze[g[i].to]);
	}
	son[a]=max(son[a],sum-sze[a]);
	if(son[a]<son[root])root=a;
}
void solve(int a,int b){
	f[a]=b;vis[a]=1;
	for(int i=head[a];i;i=g[i].last){
		if(vis[g[i].to]) continue;
		sum=sze[g[i].to];root=0;
		getroot(g[i].to,0);
		solve(root,a);
	}
}
ll val[M],s1[M],s2[M],s[M],All,Now;
void update(int a,ll b){
	val[a]+=b;
	for(int i=a;f[i];i=f[i]){
		int v=LCA::getdep(a,f[i]);
		val[f[i]]+=b;
		s1[f[i]]+=b*v;
		s2[i]+=b*v;
	}
}
ll query(int a){
	ll now=s1[a];
	for(int i=a;f[i];i=f[i]){
		int v=LCA::getdep(a,f[i]);
		now+=1ll*v*(val[f[i]]-val[i]);
		now+=s1[f[i]]-s2[i];
	}
	return now;
}

void dfs(int a,int b){
	s[a]=vv[a];
	for(int i=head[a];i;i=g[i].last){
		if(g[i].to==b) continue;
		dfs(g[i].to,a);
		s[a]+=s[g[i].to];
	}
	Now+=s[a]*(All-s[a]);
}

void Update(int a,ll b){
	b-=vv[a];
	update(a,b);
	All+=b;vv[a]+=b;
	Now+=b*query(a);
}
ll Query(int a){return (query(a)+All)*All-Now;}
int a,b,opt;
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;i++){
		scanf("%d%d",&a,&b);
		add(a,b);
	}
	for(int i=1;i<=n;i++){scanf("%lld",&vv[i]);All+=vv[i];}
	dfs(1,0);
	LCA::init();
	root=0;son[0]=M;
	getroot(1,0);
	solve(root,0);
	for(int i=1;i<=n;i++)update(i,vv[i]);
	while(m--){
		scanf("%d",&opt);
		if(opt==1){
			scanf("%d%d",&a,&b);
			Update(a,b);
		}else{
			scanf("%d",&a);
			printf("%lld\n",Query(a));
		}
	}
	return 0;
}
weixin073智慧旅游平台开发微信小程序+ssm后端毕业源码案例设计 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
python017基于Python贫困生资助管理系统带vue前后端分离毕业源码案例设计 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

VictoryCzt

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值