一、题目描述:
已知一棵包含 n (n<=1e5) 个结点的树,连通且无环,每个节点上包含一个数值,需要支持以下操作:
<1>: 1 x y z,表示将树从 x 到 y 结点最短路径上所有节点的值都加上 z 。
<2>: 2 x y,表示求树从 x 到 y 结点最短路径上所有节点的值之和。
<3>: 3 x z,表示将以 x 为根节点的子树内所有节点值都加上 z 。
<4>: 4 x,表示求以 x 为根节点的子树内所有节点值之和。
二、解题思路:
板子题,不需要思路。时间复杂度 O(
三、完整代码:
1 #include<iostream> 2 #include<cstring> 3 #define N 100010 4 #define ls(p) p<<1 5 #define rs(p) p<<1|1 6 #define ll long long 7 using namespace std; 8 ll val[N],out[N],tag[N*4]; 9 ll fa[N],du[N],top[N],size[N]; 10 ll f[N],son[N],sum[N*4],rak[N]; 11 ll n,q,s,mod,u1,v1,tot,opt,x,y,z; 12 struct EDGE{ 13 ll v; 14 ll nxt; 15 }edge[N*2]; 16 ll head[N],cnt; 17 void add(ll u,ll v) 18 { 19 edge[++cnt].v=v; 20 edge[cnt].nxt=head[u]; 21 head[u]=cnt; 22 } 23 void dfs1(ll now,ll d)//dfs1 24 { 25 du[now]=d;size[now]=1; 26 for(ll i=head[now];i!=-1;i=edge[i].nxt) 27 if(!du[edge[i].v]) 28 { 29 fa[edge[i].v]=now; 30 dfs1(edge[i].v,d+1); 31 size[now]+=size[edge[i].v]; 32 if(size[edge[i].v]>size[son[now]]) 33 son[now]=edge[i].v; 34 } 35 } 36 void dfs2(ll now,ll t)//dfs2 37 { 38 top[now]=t; 39 f[now]=++tot; 40 rak[f[now]]=now; 41 if(!son[now]) 42 { 43 out[now]=tot; 44 return ; 45 } 46 dfs2(son[now],t); 47 for(ll i=head[now];i!=-1;i=edge[i].nxt) 48 if(edge[i].v!=fa[now]&&edge[i].v!=son[now]) 49 dfs2(edge[i].v,edge[i].v); 50 out[now]=tot; 51 } 52 void push_up(ll p)//保证p有儿子 53 { 54 sum[p]=sum[ls(p)];//可能出错 55 sum[p]+=sum[rs(p)]; 56 } 57 void push_down(ll p,ll l,ll r) 58 { 59 ll mid=(l+r)>>1; 60 tag[ls(p)]+=tag[p]; 61 tag[rs(p)]+=tag[p]; 62 sum[rs(p)]+=(r-mid)*tag[p]; 63 sum[ls(p)]+=(mid-l+1)*tag[p];tag[p]=0; 64 } 65 void build(ll p,ll l,ll r) 66 { 67 if(l==r) 68 { 69 sum[p]=val[rak[l]]; 70 return ; 71 } 72 ll mid=(l+r)>>1; 73 build(ls(p),l,mid); 74 build(rs(p),mid+1,r); 75 push_up(p); 76 } 77 void update(ll p,ll l,ll r,ll nl,ll nr,ll k) 78 { 79 if(nl<=l&&r<=nr) 80 { 81 tag[p]+=k; 82 sum[p]+=(r-l+1)*k; 83 return ; 84 } 85 push_down(p,l,r);ll mid=(l+r)>>1; 86 if(nl<=mid) update(ls(p),l,mid,nl,nr,k); 87 if(nr>mid) update(rs(p),mid+1,r,nl,nr,k); 88 push_up(p); 89 } 90 void upd(ll u,ll v,ll z) 91 { 92 ll tu=top[u],tv=top[v]; 93 while(tu!=tv) 94 { 95 if(du[tu]<du[tv]) swap(u,v),swap(tu,tv); 96 update(1,1,n,f[tu],f[u],z); 97 u=fa[tu],tu=top[u]; 98 } 99 if(du[u]<du[v]) swap(u,v); 100 update(1,1,n,f[v],f[u],z); 101 }//有点难,画个图就理解了(注意重链剖分的特性) 102 ll query(ll p,ll l,ll r,ll nl,ll nr) 103 { 104 if(nl<=l&&r<=nr) 105 return sum[p]; 106 push_down(p,l,r); 107 ll mid=(l+r)>>1,ans=0; 108 if(nl<=mid) ans+=query(ls(p),l,mid,nl,nr); 109 if(nr>mid) ans+=query(rs(p),mid+1,r,nl,nr); 110 return ans; 111 } 112 ll que(ll u,ll v) 113 { 114 ll tu=top[u],tv=top[v],ans=0; 115 while(tu!=tv) 116 { 117 if(du[tu]<du[tv]) swap(u,v),swap(tu,tv); 118 ans+=query(1,1,n,f[tu],f[u]); 119 u=fa[tu],tu=top[u]; 120 } 121 if(du[u]<du[v]) swap(u,v); 122 ans+=query(1,1,n,f[v],f[u]); 123 return ans; 124 } 125 int main() 126 { 127 ios::sync_with_stdio(false); 128 cin.tie(0);cout.tie(0); 129 cin>>n>>q>>s>>mod; 130 for(ll i=1;i<=n;i++) 131 cin>>val[i]; 132 memset(head,-1,sizeof(head)); 133 for(ll i=1;i<n;i++) 134 { 135 cin>>u1>>v1; 136 add(u1,v1); 137 add(v1,u1); 138 } 139 dfs1(s,1);dfs2(s,s);build(1,1,n); 140 for(ll i=1;i<=q;i++) 141 { 142 cin>>opt; 143 if(opt==1) 144 { 145 cin>>x>>y>>z; 146 upd(x,y,z); 147 } 148 if(opt==2) 149 { 150 cin>>x>>y; 151 cout<<que(x,y)%mod<<'\n'; 152 } 153 if(opt==3) 154 { 155 cin>>x>>z; 156 update(1,1,n,f[x],out[x],z); 157 } 158 if(opt==4) 159 { 160 cin>>x; 161 cout<<query(1,1,n,f[x],out[x])%mod<<'\n'; 162 } 163 } 164 return 0; 165 }
四、写题心得:
今天下午老师讲了树链剖分,听的很明白,但是这个模板直接写了我一个下午qwq。
够长!(然而我自认为已经写得很短了)。下面说一下细节:
<1>:注意取模(这次被这个坑了好久)
<2>:两个dfs注意函数名(这个其实这次做的还不错)
<3>:有些时候为了准确性可以放弃美观(例如第54行)
<4>:线段树 update 和 query 时都要 push_down
<5>:build 线段树时记得要转换为 dfn 序(求rak数组)
<6>:第100行个人觉得不是很好理解(建议画个图)
<7>:out[x]已经是 dfn 序,不要再套一个 dfn 数组 !(又被坑了!)
写完了,拜拜!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】