洛谷P3384 【模板】轻重链剖分/树链剖分
题目链接:https://www.luogu.com.cn/problem/P3384
大意
给定一个包含N个结点的树,每个节点上包含一个数值,存在一下四种操作:
1 x y z
,表示将树从 x 到 y 结点最短路径上所有节点的值都加上 z。2 x y
,表示求树从 x 到 y 结点最短路径上所有节点的值之和。3 x z
,表示将以 x 为根节点的子树内所有节点值都加上 z。4 x
表示求以 x 为根节点的子树内所有节点值之和
思路
树链剖分出dfs序和时间戳后用线段树维护dfs序(后序操作都在线段树上进行)
1操作维护x到y结点最短路径上节点的值,因为重链上的节点时间戳连续可每个重链进行操作,离开重链只需要在重链头结点往上一个父亲结点即可跳出该重链。当x和y处于同一重链时直接操作后跳出。
2操作同1操作唯一不同为结点加权值操作替换为结点权值相加
3操作因为同一子树结点时间戳连续,从该子树结点数,即可知道该区间范围,那么只需要操作x的时间戳(下面叫做cnt[x])到cnt[x]+siz[x]-1 这一区间值加上z即可(siz[x]为以x为根节点的子树结点数,包含x自身)。
4操作同3操作加上各节点权值即可
另区间加法那必然是要用线段树懒标记的
CODE
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define lch (k*2)
#define rch (k*2+1)
#define mid ((l+r)/2)
const ll mod=1e9+7;
const ll N=2e5+7;
ll n,m,r,p;
ll c[N],ch[N];
ll e[N],h[N*2],ne[N*2],idx=1,num;
ll tr[N*4],lazy[N*4];
ll dep[N*2],siz[N*2],fa[N*2],son[N*2],tp[N*2],cnt[N*2];
void init(ll k,ll l,ll r)//初始化
{
if(l>=r)
{
tr[k]=ch[l];
return;
}
init(lch,l,mid);
init(rch,mid+1,r);
tr[k]=(tr[lch]+tr[rch]+p)%p;
}
void pushdown(ll k,ll l,ll r)//向下传递懒标记
{
if(!lazy[k]) return;
lazy[lch]=(lazy[k]+lazy[lch])%p;
lazy[rch]=(lazy[k]+lazy[rch])%p;
tr[lch]=(lazy[k]*(mid-l+1)+tr[lch])%p;
tr[rch]=(lazy[k]*(r-mid)+tr[rch])%p;
lazy[k]=0;
}
void update(ll k,ll l,ll r,ll ql,ll qr,ll val)//区间加
{
if(ql<=l&&r<=qr)
{
tr[k]=(tr[k]+(r-l+1)*val);
lazy[k]=(lazy[k]+val);
return ;
}
pushdown(k,l,r);
if(mid>=ql)
{
update(lch,l,mid,ql,qr,val);
}
if(mid+1<=qr)
{
update(rch,mid+1,r,ql,qr,val);
}
tr[k]=(tr[lch]+tr[rch]+p)%p;
}
ll qry(ll k,ll l,ll r,ll ql,ll qr)//区间求和
{
if(ql<=l&&r<=qr)
{
return tr[k];
}
pushdown(k,l,r);
ll ans=0;
if(mid>=ql)
{
ans=(ans+qry(lch,l,mid,ql,qr))%p;
}
if(mid+1<=qr)
{
ans=(ans+qry(rch,mid+1,r,ql,qr))%p;
}
return ans%p;
}
/* 线段树分界线 */
void add(ll a,ll b)//前链星
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
ll dfs1(ll u,ll f,ll deep)//标记重儿子、轻儿子、结点深度、子树大小
{
dep[u]=deep;
fa[u]=f;
siz[u]=1;
ll nmax=-1;
for(ll i=h[u];~i;i=ne[i])
{
if(e[i]==f)
continue;
siz[u]+=dfs1(e[i],u,deep+1);
if(siz[e[i]]>nmax)
{
nmax=siz[e[i]];
son[u]=e[i];
}
}
return siz[u];
}
void dfs2(ll u,ll top)//获取时间戳,dfs序,重链头结点
{
cnt[u]=++num;//时间戳,通过时间戳进行线段树的位置确定
ch[num]=c[u];//dfs序
tp[u]=top;
if(!son[u]) return ;
dfs2(son[u],top);
for(ll i=h[u];~i;i=ne[i])
{
if(!cnt[e[i]])
dfs2(e[i],e[i]);
}
}
void treesum(ll x,ll y)
{
ll ans=0;
while(tp[x]!=tp[y])
{
if(dep[tp[x]]<dep[tp[y]])
swap(x,y);
ans=(ans+qry(1,1,N,cnt[tp[x]],cnt[x]))%p;
x=fa[tp[x]];
}
if(dep[x]>dep[y])
swap(x,y);
ans=(ans+qry(1,1,N,cnt[x],cnt[y]))%p;
cout<<ans<<endl;
}
void treeadd(ll x,ll y,ll val)
{
while(tp[x]!=tp[y])
{
if(dep[tp[x]]<dep[tp[y]])
swap(x,y);
update(1,1,N,cnt[tp[x]],cnt[x],val);
x=fa[tp[x]];
}
if(dep[x]>dep[y])
swap(x,y);
update(1,1,N,cnt[x],cnt[y],val);
}
int main()
{
ios::sync_with_stdio(0);
cin>>n>>m>>r>>p;
memset(h,-1,sizeof(h));
for(ll i=1;i<=n;i++)
{
cin>>c[i];
c[i]=c[i]%p;
}
for(ll i=1;i<n;i++)
{
ll x,y;
cin>>x>>y;
add(x,y);
add(y,x);
}
dfs1(r,0,1);
dfs2(r,r);
init(1,1,N);
while(m--)
{
ll op,x,y,z;
cin>>op;
if(op==1)
{
cin>>x>>y>>z;
treeadd(x,y,z);
}
if(op==2)
{
cin>>x>>y;
treesum(x,y);
}
if(op==3)
{
cin>>x>>z;
update(1,1,N,cnt[x],cnt[x]+siz[x]-1,z%p);
}
if(op==4)
{
cin>>x;
cout<<qry(1,1,N,cnt[x],cnt[x]+siz[x]-1)<<endl;
}
}
}
2021杭电第二场多校I love tree
题目链接:https://acm.hdu.edu.cn/showproblem.php?pid=6962
大意
给定一颗n结点的树
现存在两种操作:
1操作将a到b最短路径遍历的第i个结点加上 i 2 i^2 i2;
2操作将x结点的权值输出
思路
很明显的树链剖分+线段树
首先我们假设线段树维护一个数组的话在区间[a,b]内怎样操作。
区间[a,b]内每个结点x存在加上值 ( x − a + 1 ) 2 = ( x − ( a − 1 ) ) 2 (x-a+1)^2=(x-(a-1))^2 (x−a+1)2=(x−(a−1))2;
公式拆分得到 x 2 − 2 ∗ ( a − 1 ) ∗ x + ( a − 1 ) 2 x ^ 2 - 2 * (a-1) * x + (a-1) ^ 2 x2−2∗(a−1)∗x+(a−1)2
所以我们可以用三颗线段树分别维护每个单项式的系数(当然你也可以一颗线段树维护三个值),最后输出再根据公式输出即可。
同时也存在b大于a时,加上的值为 ( b − x + 1 ) 2 (b-x+1)^2 (b−x+1)2
即 ( b − x + 1 ) 2 = ( x − ( b + 1 ) ) 2 (b-x+1)^2=(x-(b+1))^2 (b−x+1)2=(x−(b+1))2
跳过树链剖分基本操作,我们看看怎么结合线段树(线段树维护的是一个dfs序的数组)
首先我们用lca找到a,b两节点的公共祖先,两节点深度之和-2*祖先深度+1得到a,b两节点最短路径结点数
维护两个数q,p,分别表示左右两边分配到的数(因为我们树链分段后只会从左右两边的数开始分配到线段树)
最后我们只要两个结点不断往上跳的同时判断一下就ok啦!
因为往上跳的同时不可避免的出现逆序的情况,参考上面两个式子,复用一下线段树也就ok了。
CODE
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define lch (k*2)
#define rch (k*2+1)
#define mid ((l+r)/2)
const ll mod=1e9+7;
const ll N=2e5+7;
ll n,m;
ll e[N],h[N*2],ne[N*2],idx=1,num;
ll dep[N*2],siz[N*2],fa[N*2],son[N*2],tp[N*2],cnt[N*2];
struct node//建三棵线段树用结构体减一点代码量
{
ll tr[N*4];
void pushdown(ll k)//向下传递懒标记
{
if(!tr[k]) return;
tr[lch]+=tr[k];
tr[rch]+=tr[k];
tr[k]=0;
}
void update(ll k,ll l,ll r,ll ql,ll qr,ll val)//区间加
{
if(ql<=l&&r<=qr)
{
tr[k]+=val;
return ;
}
pushdown(k);
if(ql<=mid)
{
update(lch,l,mid,ql,qr,val);
}
if(mid+1<=qr)
{
update(rch,mid+1,r,ql,qr,val);
}
}
ll qry(ll k,ll l,ll r,ll tot)//单点值
{
if(l>=r)
{
return tr[k];
}
pushdown(k);
if(mid>=tot)
{
return qry(lch,l,mid,tot);
}
else
{
return qry(rch,mid+1,r,tot);
}
}
}st[5];
/* 线段树分界线 */
void add(ll a,ll b)//前链星
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
ll dfs1(ll u,ll f,ll deep)//标记重儿子、轻儿子、结点深度、子树大小
{
dep[u]=deep;
fa[u]=f;
siz[u]=1;
ll nmax=-1;
for(ll i=h[u];~i;i=ne[i])
{
if(e[i]==f)
continue;
siz[u]+=dfs1(e[i],u,deep+1);
if(siz[e[i]]>nmax)
{
nmax=siz[e[i]];
son[u]=e[i];
}
}
return siz[u];
}
void dfs2(ll u,ll top)//获取时间戳,dfs序,重链头结点
{
cnt[u]=++num;//时间戳,通过时间戳进行线段树的位置确定
tp[u]=top;
if(!son[u]) return ;
dfs2(son[u],top);
for(ll i=h[u];~i;i=ne[i])
{
if(!cnt[e[i]])
dfs2(e[i],e[i]);
}
}
ll lca(int x,int y)
{
while(tp[x]!=tp[y])
{
if(dep[tp[x]]<dep[tp[y]])
swap(x,y);
x=fa[tp[x]];
}
if(dep[x]>dep[y])
{
swap(x,y);
}
return x;
}
void treeadd(ll x,ll y,ll k)
{
ll q=1,p=k;
while(tp[x]!=tp[y])
{
if(dep[tp[x]]>dep[tp[y]])
{
q+=dep[x]-dep[tp[x]];
st[1].update(1,1,n,cnt[tp[x]],cnt[x],(q+cnt[tp[x]])*(q+cnt[tp[x]]));
st[2].update(1,1,n,cnt[tp[x]],cnt[x],(q+cnt[tp[x]]));
st[3].update(1,1,n,cnt[tp[x]],cnt[x],1);
x=fa[tp[x]];
q++;
}
else
{
p-=dep[y]-dep[tp[y]];
st[1].update(1,1,n,cnt[tp[y]],cnt[y],(-p+cnt[tp[y]])*(-p+cnt[tp[y]]));
st[2].update(1,1,n,cnt[tp[y]],cnt[y],(-p+cnt[tp[y]]));
st[3].update(1,1,n,cnt[tp[y]],cnt[y],1);
y=fa[tp[y]];
p--;
}
}
if(dep[x]>dep[y])
{
q+=dep[x]-dep[y];
st[1].update(1,1,n,cnt[y],cnt[x],(q+cnt[y])*(q+cnt[y]));
st[2].update(1,1,n,cnt[y],cnt[x],(q+cnt[y]));
st[3].update(1,1,n,cnt[y],cnt[x],1);
}
else
{
p-=dep[y]-dep[x];
st[1].update(1,1,n,cnt[x],cnt[y],(-p+cnt[x])*(-p+cnt[x]));
st[2].update(1,1,n,cnt[x],cnt[y],(-p+cnt[x]));
st[3].update(1,1,n,cnt[x],cnt[y],1);
}
}
void treeqry(ll x)
{
//cout<<st[1].qry(1,1,n,cnt[x])<<"] ["<<st[2].qry(1,1,n,cnt[x])<<"] ["<<st[3].qry(1,1,n,cnt[x])<<"] ["<<cnt[x]<<endl;
cout<<st[1].qry(1,1,n,cnt[x])-st[2].qry(1,1,n,cnt[x])*2*cnt[x]+st[3].qry(1,1,n,cnt[x])*cnt[x]*cnt[x]<<endl;
}
int main()
{
ios::sync_with_stdio(0);
cin>>n;
memset(h,-1,sizeof(h));
for(ll i=1;i<n;i++)
{
ll x,y;
cin>>x>>y;
add(x,y);
add(y,x);
}
dfs1(1,0,1);
dfs2(1,1);
cin>>m;
while(m--)
{
int op,x,y;
cin>>op;
if(op==1)
{
cin>>x>>y;
int xx=lca(x,y);
treeadd(x,y,dep[x]+dep[y]-2*dep[xx]+1);
}
else
{
cin>>x;
treeqry(x);
}
}
return 0;
}