2019 ICPC上海 F.A Simple Problem On A Tree(树链剖分)

题目描述

题目链接

题目大意

有一棵含有n个节点的树,树上的每个节点都有一个权值w。有四种操作:
1、将u节点到v节点路径上的所有点的权值都改为w。
2、将u节点到v节点路径上的所有点的权值都加上w。
3、将u节点到v节点路径上的所有点的权值都乘上w。
4、求u节点到v节点路径上的所有点的权值的三次方和。

题目分析

这道题基本上就是由树链剖分和线段树维护区间加、乘操作两部分组合而成。除此之外,最大的难点就是如何用线段树维护区间的三次方和。
我们可以分开来看:
1、如何维护区间加数
( a + x ) 3 + ( b + x ) 3 + ( c + x ) 3 = a 3 + b 3 + c 3 + 3 x 3 + 3 x ( a 2 + b 2 + c 2 ) + 3 x 2 ( a + b + c ) (a+x)^3+(b+x)^3+(c+x)^3=a^3+b^3+c^3+3x^3+3x(a^2+b^2+c^2)+3x^2(a+b+c) (a+x)3+(b+x)3+(c+x)3=a3+b3+c3+3x3+3x(a2+b2+c2)+3x2(a+b+c)

从 这 个 公 式 中 我 们 可 以 看 出 , 如 果 要 维 护 三 次 方 和 加 数 , 那 么 我 们 就 需 要 同 时 维 护 这 个 序 列 的 一 次 方 和 以 及 二 次 从这个公式中我们可以看出,如果要维护三次方和加数,那么我们就需要同时维护这个序列的一次方和以及二次
方 和 ( 二 次 方 和 的 加 数 维 护 大 家 可 以 根 据 三 次 方 和 的 公 式 自 己 推 导 一 下 ) 方和(二次方和的加数维护大家可以根据三次方和的公式自己推导一下)

2、如何维护区间乘数
( a x ) 3 + ( b x ) 3 + ( c x ) 3 = ( a 3 + b 3 + c 3 ) x 3 (ax)^3+(bx)^3+(cx)^3=(a^3+b^3+c^3)x^3 (ax)3+(bx)3+(cx)3=(a3+b3+c3)x3
( a x ) 2 + ( b x ) 2 + ( c x ) 2 = ( a 2 + b 2 + c 2 ) x 2 (ax)^2+(bx)^2+(cx)^2=(a^2+b^2+c^2)x^2 (ax)2+(bx)2+(cx)2=(a2+b2+c2)x2

因 此 , 维 护 乘 法 操 作 我 们 只 需 要 给 对 应 的 次 方 和 乘 上 乘 数 的 对 应 次 方 即 可 因此,维护乘法操作我们只需要给对应的次方和乘上乘数的对应次方即可

代码如下
#include <iostream>
#include <cmath>
#include <cstdio>
#include <set>
#include <string>
#include <cstring>
#include <map>
#include <algorithm>
#include <stack>
#include <queue>
#include <bitset>
#define LL long long
#define ULL unsigned long long
#define PII pair<int,int>
#define PLL pair<LL,LL>
#define PDD pair<double,double>
#define x first
#define y second
using namespace std;
const int N=1e5+5,mod=1e9+7;
LL w[N],nw[N];
int h[N],e[N*2],ne[N*2],idx;
int id[N],cnt;
int dep[N],sz[N],top[N],fa[N],son[N];
struct Node{
	int l,r;
	LL sum1,sum2,sum3;		//sumx   表示序列的x次方和
	LL add,mul;				//加法懒标记和乘法懒标记
}tr[N*4];
void add(int a,int b)
{
	e[idx]=b;
	ne[idx]=h[a];
	h[a]=idx++;
}
void dfs1(int u,int father,int depth)
{
	dep[u]=depth,fa[u]=father,sz[u]=1;
	for(int i=h[u];~i;i=ne[i])
	{
		int v=e[i];
		if(v==father) continue;
		dfs1(v,u,depth+1);
		sz[u]+=sz[v];
		if(sz[son[u]]<sz[v]) son[u]=v;
	}
}
void dfs2(int u,int t)
{
	id[u]=++cnt,nw[cnt]=w[u],top[u]=t;
	if(!son[u]) return;
	dfs2(son[u],t);
	for(int i=h[u];~i;i=ne[i])
	{
		int v=e[i];
		if(v==fa[u]||v==son[u]) continue;
		dfs2(v,v);
	}
}
void pushup(int u)
{
	tr[u].sum1=(tr[u<<1].sum1+tr[u<<1|1].sum1)%mod;
	tr[u].sum2=(tr[u<<1].sum2+tr[u<<1|1].sum2)%mod;
	tr[u].sum3=(tr[u<<1].sum3+tr[u<<1|1].sum3)%mod;
}
void push(Node& u,LL add,LL mul)		//懒标记下放函数
{	//直接根据公式来写即可(注意如果不及时取模有可能爆long long)
	if(mul!=1)
	{
		LL mul2=mul*mul%mod,mul3=mul2*mul%mod;
		u.sum3=u.sum3*mul3%mod;
		u.sum2=u.sum2*mul2%mod;
		u.sum1=u.sum1*mul%mod;
		
		u.mul=u.mul*mul%mod;
		u.add=u.add*mul%mod;
	}
	if(add!=0)
	{
		LL add2=add*add%mod,add3=add2*add%mod;
		u.sum3=(u.sum3+add3*(u.r-u.l+1)%mod+3*add2%mod*u.sum1%mod+3*add%mod*u.sum2%mod)%mod;
		u.sum2=(u.sum2+add2*(u.r-u.l+1)%mod+2*add%mod*u.sum1%mod)%mod;
		u.sum1=(u.sum1+add*(u.r-u.l+1)%mod)%mod;
		
		u.add=(u.add+add)%mod;
	}
}
void pushdown(int u)
{
	push(tr[u<<1],tr[u].add,tr[u].mul);			//懒标记下传
	push(tr[u<<1|1],tr[u].add,tr[u].mul);
	tr[u].add=0,tr[u].mul=1;
}
void build(int u,int l,int r)				//建树
{
	if(l==r)
	{
		LL w2=nw[l]*nw[l]%mod;
		LL w3=w2*nw[l]%mod;
		tr[u]={l,r,nw[l],w2,w3,0,1};
	}
	else {
		tr[u]={l,r,0,0,0,0,1};
		int mid=l+r>>1;
		build(u<<1,l,mid),build(u<<1|1,mid+1,r);
		pushup(u);
	}
}
void update(int u,int l,int r,int add,int mul)			//区间更新
{
	if(l<=tr[u].l&&tr[u].r<=r) push(tr[u],add,mul);
	else {
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1;
		if(l<=mid) update(u<<1,l,r,add,mul);
		if(mid<r) update(u<<1|1,l,r,add,mul);
		pushup(u); 
	}
}
LL query(int u,int l,int r)						//区间查询
{
	if(l<=tr[u].l&&tr[u].r<=r) return tr[u].sum3;
	pushdown(u);
	int mid=tr[u].l+tr[u].r>>1;
	LL sum=0;
	if(l<=mid) sum=query(u<<1,l,r);
	if(mid<r) sum=(sum+query(u<<1|1,l,r))%mod;
	return sum; 
}
void updatePath(int u,int v,int add,int mul)		//树剖的路径更新 
{
	while(top[u]!=top[v])
	{
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		update(1,id[top[u]],id[u],add,mul);
		u=fa[top[u]];
	}
	if(dep[u]<dep[v]) swap(u,v);
	update(1,id[v],id[u],add,mul);
}
LL queryPath(int u,int v)						//树剖的路径查询
{
	LL res=0;
	while(top[u]!=top[v])
	{
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		res=(res+query(1,id[top[u]],id[u]))%mod;
		u=fa[top[u]];
	}
	if(dep[u]<dep[v]) swap(u,v);
	res=(res+query(1,id[v],id[u]))%mod;
	return res; 
}
int main()
{
	cin.tie(0);
	ios::sync_with_stdio(false);
	int t;
	cin>>t;
	for(int T=1;T<=t;T++)
	{
		memset(h,-1,sizeof h);			//初始化
		memset(son,0,sizeof son);
		idx=cnt=0;
		int n,m;
		cin>>n;
		for(int i=1;i<n;i++)
		{
			int u,v;
			cin>>u>>v;
			add(u,v),add(v,u);
		}
		for(int i=1;i<=n;i++) cin>>w[i];
		dfs1(1,-1,1);
		dfs2(1,1);
		build(1,1,n);
		cout<<"Case #"<<T<<":"<<endl;
		cin>>m;
		while(m--)
		{
			int op,u,v;
			cin>>op>>u>>v;
			if(op==1)
			{
				int x;
				cin>>x;
				updatePath(u,v,x,0);
			}
			else if(op==2)
			{
				int add;
				cin>>add;
				updatePath(u,v,add,1);
			}
			else if(op==3)
			{
				int mul;
				cin>>mul;
				updatePath(u,v,0,mul);
			}
			else cout<<queryPath(u,v)<<endl;
		}
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

lwz_159

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

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

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

打赏作者

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

抵扣说明:

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

余额充值