G. Matematical Transformation(树链剖分+线段树)
Posted 斗奋力努
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了G. Matematical Transformation(树链剖分+线段树)相关的知识,希望对你有一定的参考价值。
G. Matematical Transformation(树链剖分+线段树)
G. Matematical Transformation
题意:
一颗有n个节点的有根树,根节点是1,初始每个节点的值为0
现在进行q次操作
操作0: 0 u v 输出节点u到节点v的路径和
操作1: 1 u v k 对节点u的子树,每个节点的价值加上
v
+
k
∗
d
v+k*d
v+k∗d,d为该节点i到节点u的深度差
(
d
[
i
]
−
d
[
u
]
)
(d[i]-d[u])
(d[i]−d[u])
思路:
树链剖分套路题。
每次进行操作0,其实就是树链剖分中的求路径和(链的长度)
每次进行操作1,我们知道对于某个需要修改的节点i来说,
其值会加上
v
+
k
∗
d
=
v
+
k
∗
(
d
e
p
[
i
]
−
d
e
p
[
u
]
)
=
(
v
+
k
∗
d
e
p
[
u
]
)
−
(
k
∗
d
e
p
[
i
]
)
v+k*d=v+k*(dep[i]-dep[u])=(v+k*dep[u])-(k*dep[i])
v+k∗d=v+k∗(dep[i]−dep[u])=(v+k∗dep[u])−(k∗dep[i])
我们发现,对于每次修改:
第一个括号
(
v
+
k
∗
d
e
p
[
u
]
)
(v+k*dep[u])
(v+k∗dep[u]),对于u的整颗子树(要修改区间)的节点是一个定值。
对于线段树的每个节点,我们可以单独维护一个标记
(
a
d
d
s
)
(adds)
(adds)。来表示每次区间修改的定值情况
第二个括号
(
k
∗
d
e
p
[
i
]
)
(k*dep[i])
(k∗dep[i]),对于u的整颗子树(要修改区间)的节点是一个不定值。
但是单独对于每个节点来说,每次修改都是自己的深度
d
e
p
[
i
]
dep[i]
dep[i],乘上修改的
−
k
-k
−k。
对于线段树中的每个节点,我们可以另外单独维护一个标记
(
a
d
d
k
)
(addk)
(addk),来记录每次区间修改的不定值情况
综上,我们发现每次修改都与深度有关。
所以对于线段树的每个节点,我们同时还需要维护一个区间节点的深度和
(
s
u
m
d
e
p
)
(sumdep)
(sumdep),当然区间和
(
s
u
m
)
(sum)
(sum)肯定不能少。
注意一点,答案爆掉longlong了,要用__int128。
最后,码上树链剖分模板就能自动ac。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+5,M=2*N;
int n,q;
int to[M],ne[M],fir[N],idx;
int id[N],nd[N],cnt;
int dep[N],sz[N],top[N],son[N],fa[N];
struct node
int l,r;
__int128 adds,addk;
__int128 sumdep,sum;
tr[N<<2];
void print(__int128 x)
if(x<0)
x=-x;
putchar('-');
if(x>9) print(x/10);
putchar(x%10+'0');
//链式前
void add(int u,int v)to[idx]=v,ne[idx]=fir[u],fir[u]=idx++;
//树链剖分模板
void dfs1(int u,int father,int depth)
sz[u]=1,fa[u]=father,dep[u]=depth;
for(int i=fir[u];~i;i=ne[i])
int v=to[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,nd[cnt]=dep[u],top[u]=t;
if(!son[u]) return;
dfs2(son[u],t);
for(int i=fir[u];~i;i=ne[i])
int v=to[i];
if(v==fa[u]||v==son[u]) continue;
dfs2(v,v);
//线段树模板
void build(int u,int l,int r)
tr[u]=l,r,0,0,0,0;
if(l==r)
tr[u].sumdep=nd[l];
return;
int mid=(l+r)>>1;
build(u<<1,l,mid),build(u<<1|1,mid+1,r);
tr[u].sumdep=tr[u<<1].sumdep+tr[u<<1|1].sumdep;
void pushdown(int u)
auto &root=tr[u],&left=tr[u<<1],&right=tr[u<<1|1];
if(root.addk||root.adds)
left.adds+=root.adds;
left.addk+=root.addk;
left.sum+=(left.r-left.l+1)*root.adds+left.sumdep*root.addk;
right.adds+=root.adds;
right.addk+=root.addk;
right.sum+=(right.r-right.l+1)*root.adds+right.sumdep*root.addk;
root.adds=root.addk=0;
void modify(int u,int l,int r,__int128 tags,__int128 tagk)
if(l<=tr[u].l&&tr[u].r<=r)
tr[u].adds+=tags;
tr[u].addk+=tagk;
tr[u].sum+=(tr[u].r-tr[u].l+1)*tags+tr[u].sumdep*tagk;
return;
pushdown(u);
int mid=(tr[u].l+tr[u].r)>>1;
if(l<=mid) modify(u<<1,l,r,tags,tagk);
if(r>mid) modify(u<<1|1,l,r,tags,tagk);
tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
//修改子树
void modify_tree(int u,__int128 tags,__int128 tagk)
modify(1,id[u],id[u]+sz[u]-1,tags,tagk);
__int128 query(int u,int l,int r)
if(l<=tr[u].l&&tr[u].r<=r) return tr[u].sum;
pushdown(u);
int mid=(tr[u].l+tr[u].r)>>1;
__int128 sum=0;
if(l<=mid) sum+=query(u<<1,l,r);
if(r>mid) sum+=query(u<<1|1,l,r);
return sum;
//查询路径
__int128 query_path(int u,int v)
__int128 res=0;
while(top[u]!=top[v])
if(dep[top[u]]<dep[top[v]]) swap(u,v);
res+=query(1,id[top[u]],id[u]);
u=fa[top[u]];
if(dep[u]<dep[v]) swap(u,v);
res+=query(1,id[v],id[u]);
return res;
signed main()
scanf("%lld",&n);
for(int i=1;i<=n;i++) fir[i]=-1;
for(int i=1;i<n;i++)
int u,v; scanf("%lld%lld",&u,&v);
add(u,v),add(v,u);
dfs1(1,0,1);
dfs2(1,1);
build(1,1,n);
scanf("%lld",&q);
while(q--)
int op; scanf("%lld",&op);
if(op==0)
int u,v; scanf("%lld%lld",&u,&v);
__int128 ans=query_path(u,v);
print(ans);
puts("");
else
int u,v,k; scanf("%lld%lld%lld",&u,&v,&k);
modify_tree(u,v-k*dep[u],k);
以上是关于G. Matematical Transformation(树链剖分+线段树)的主要内容,如果未能解决你的问题,请参考以下文章
G. Matematical Transformation(树链剖分+线段树)