[Luogu-P3676]小清新資料結構題-題解

VictoryCzt發表於2019-01-05

題目地址

本來應該用動態點分治來做的,複雜度是O(nlogn)O(nlogn),但是發現樹鏈剖分好像挺好寫的XD,於是O(nlog2n)O(nlog^2n)過了…


題意可以去看原題面。

其實我們資料中詢問以1號點為根的,從這裡入手,我們先將每個點的子樹值的和求出來,記為s1s_1,然後用樹剖把它變成序列,然後再記S1(u)=vs1(v)S_1(u)=\sum_{v}s_1(v),其中vvuu子樹中的點,記S2(u)=S1(u)2S_2(u)=S_1(u)^2,我們樹剖後用線段樹維護這兩個值。

考慮序列上維護權值和和和的平方(讀起來有點怪XD)。

首先,以一號點為根的答案就是:

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

考慮修改操作:

修改一個點uu的權值,我們考慮其變化量,假設為valval,那麼會影響到的就是從1u1\sim u這條路上的所有點的答案,首先加上變化量valval,答案就變成了:

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 \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

所以每次增加的就是val2val^22×val×S1(n)2\times val\times S_1(n),用個lazylazy標記,區間修改即可。


現在考慮根不是11的情況:

首先記原來根為11的答案為ans1ans_1,然後我們考慮,當根變成rr時,會發生變化的只有1r1\sim r這條路上的點的貢獻,所以我們記原來每個點的s1(i)s_1(i)aia_i,換根後的為bib_i,那麼新的答案就為:

i=1ns1(i)2v[1r]ai2+v[1r]bi2=ans1v[1r]ai2+v[1r]bi2 \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

然後我們看對於改變後的bib_i不好求,而aia_i開始已經預處理出來了,所以我們考慮bib_iaia_i的關係:

可以發現在1r1\sim r這條路上,假如一個點uu的下一個點(從1到r的下一個點)為vv,那麼就有:

av+bu=a1=br a_v+b_u=a_1=b_r

所以我們令bu=a1avb_u=a_1-a_v,重新編號的話就是bi=a1ai+1b_i=a_1-a_{i+1},帶回原式,假如1r1\sim rkk個點:

ans=ans1i=1kai2+i=1k1(a1ai+1)2+a12=ans1i=2kai2+i=2k(a122a1ai+ai2)=ans1i=2kai2+(k1)a122a1i=2kai+i=2kai2=ans1+(k1)a122a1i=2kai=ans1+a1((k1)a12i=2kai) \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}

aia_i又等於s1(i)s_1(i),所以帶入式子,每次算一下就好啦,複雜度O(log2n)O(log^2n)

用上面線段樹維護的兩個S1,S2S_1,S_2表示,也就是:

S2(1)+S1(1)(kS1(1)2i=2ks1(i)) S_2(1)+S_1(1)\cdot(k\cdot S_1(1)-2\cdot \sum_{i=2}^ks_1(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;
}

相關文章