洛谷 P3384 【模板】树链剖分-树链剖分(点权)(路径节点更新、路径求和、子树节点更新、子树求和)模板-备注结合一下以前写的题目,懒得写很详细的注释
P3384 【模板】树链剖分
题目描述
如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:
操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z
操作2: 格式: 2 x y 表示求树从x到y结点最短路径上所有节点的值之和
操作3: 格式: 3 x z 表示将以x为根节点的子树内所有节点值都加上z
操作4: 格式: 4 x 表示求以x为根节点的子树内所有节点值之和
输入输出格式
输入格式:
第一行包含4个正整数N、M、R、P,分别表示树的结点个数、操作个数、根节点序号和取模数(即所有的输出结果均对此取模)。
接下来一行包含N个非负整数,分别依次表示各个节点上初始的数值。
接下来N-1行每行包含两个整数x、y,表示点x和点y之间连有一条边(保证无环且连通)
接下来M行每行包含若干个正整数,每行表示一个操作,格式如下:
操作1: 1 x y z
操作2: 2 x y
操作3: 3 x z
操作4: 4 x
输出格式:
输出包含若干行,分别依次表示每个操作2或操作4所得的结果(对P取模)
输入输出样例
说明
时空限制:1s,128M
数据规模:
对于30%的数据: N \leq 10, M \leq 10N≤10,M≤10
对于70%的数据: N \leq {10}^3, M \leq {10}^3N≤103,M≤103
对于100%的数据: N \leq {10}^5, M \leq {10}^5N≤105,M≤105
( 其实,纯随机生成的树LCA+暴力是能过的,可是,你觉得可能是纯随机的么233 )
样例说明:
树的结构如下:
各个操作如下:
故输出应依次为2、21(重要的事情说三遍:记得取模)
题意很直接,直接模板题。
写了两天,最后发现,加边时add(v,u)的括号写成[ ]了,可真是捞啊。
写了注释。
代码:
1 //洛谷-P3384 【模板】树链剖分-树链剖分 2 #include<iostream> 3 #include<cstdio> 4 #include<cstring> 5 #include<algorithm> 6 #include<bitset> 7 #include<cassert> 8 #include<cctype> 9 #include<cmath> 10 #include<cstdlib> 11 #include<ctime> 12 #include<deque> 13 #include<iomanip> 14 #include<list> 15 #include<map> 16 #include<queue> 17 #include<set> 18 #include<stack> 19 #include<vector> 20 using namespace std; 21 typedef long long ll; 22 23 const double PI=acos(-1.0); 24 const double eps=1e-6; 25 //const ll mod=1e9+7; 26 const int inf=0x3f3f3f3f; 27 const int maxn=2e5+10; 28 const int maxm=100+10; 29 #define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); 30 #define lson l,m,rt<<1 31 #define rson m+1,r,rt<<1|1 32 33 int sum[maxn<<2],lazy[maxn<<2]; 34 int n,m,r,mod; 35 int head[maxn],tot; 36 37 int son[maxn],tid[maxn],fa[maxn],cnt,dep[maxn],siz[maxn],top[maxn]; 38 int w[maxn],wt[maxn]; 39 40 struct Edge{ 41 int to,next; 42 }edge[maxn]; 43 44 void add(int u,int v)//链式前向星存边 45 { 46 edge[tot].to=v; 47 edge[tot].next=head[u]; 48 head[u]=tot++; 49 } 50 51 void init()//初始化 52 { 53 memset(head,-1,sizeof(head)); 54 tot=0;cnt=0; 55 } 56 57 //线段树部分 58 void pushup(int rt)//上传lazy标记 59 { 60 sum[rt]=(sum[rt<<1]+sum[rt<<1|1])%mod; 61 } 62 63 void pushdown(int rt,int m)//下放lazy标记 64 { 65 if(lazy[rt]){ 66 lazy[rt<<1]+=lazy[rt]; 67 lazy[rt<<1|1]+=lazy[rt]; 68 sum[rt<<1]+=(m-(m>>1))*lazy[rt],sum[rt<<1]%=mod; 69 sum[rt<<1|1]+=(m>>1)*lazy[rt],sum[rt<<1|1]%=mod; 70 lazy[rt]=0; 71 } 72 } 73 74 void build(int l,int r,int rt) 75 { 76 lazy[rt]=0; 77 if(l==r){ 78 sum[rt]=wt[l],sum[rt]%=mod;//新的编号点权 79 return ; 80 } 81 82 int m=(l+r)>>1; 83 build(lson); 84 build(rson); 85 pushup(rt); 86 } 87 88 void update(int L,int R,int c,int l,int r,int rt)//区间更新 89 { 90 if(L<=l&&r<=R){ 91 lazy[rt]+=c; 92 sum[rt]+=c*(r-l+1),sum[rt]%=mod; 93 return ; 94 } 95 96 pushdown(rt,r-l+1); 97 int m=(l+r)>>1; 98 if(L<=m) update(L,R,c,lson); 99 if(R> m) update(L,R,c,rson); 100 pushup(rt); 101 } 102 103 int query(int L,int R,int l,int r,int rt) 104 { 105 if(L<=l&&r<=R){ 106 return sum[rt]; 107 } 108 109 int ret=0; 110 pushdown(rt,r-l+1); 111 int m=(l+r)>>1; 112 if(L<=m) ret+=query(L,R,lson),ret%=mod; 113 if(R> m) ret+=query(L,R,rson),ret%=mod; 114 return ret; 115 } 116 117 //树链剖分部分 118 void dfs1(int u,int father) 119 { 120 siz[u]=1;//记录每个节点子树大小 121 fa[u]=father;//标记节点的父亲 122 dep[u]=dep[father]+1;//标记深度 123 for(int i=head[u];~i;i=edge[i].next){ 124 int v=edge[i].to; 125 if(v==father) continue;//如果连接的是当前节点的父亲节点,则不处理 126 dfs1(v,u); 127 siz[u]+=siz[v];//直接子树节点相加,当前节点的size加上子节点的size 128 if(siz[v]>siz[son[u]]){//如果没有设置过重节点son或者子节点v的size大于之前记录的重节点son,进行更新,保存重儿子 129 son[u]=v;//标记u的重儿子为v 130 } 131 } 132 } 133 134 void dfs2(int u,int tp) 135 { 136 top[u]=tp;//标记每个重链的顶端 137 tid[u]=++cnt;//每个节点剖分以后的新编号(dfs的执行顺序) 138 wt[cnt]=w[u];//新编号的对应权值 139 if(!son[u]) return ;//如果当前节点没有处在重链上,则不处理,否则就将这条重链上的所有节点都设置成起始的重节点 140 dfs2(son[u],tp);//搜索下一个重儿子 141 for(int i=head[u];~i;i=edge[i].next){ 142 int v=edge[i].to; 143 if(v==fa[u]||v==son[u]) continue;//处理轻儿子,如果连接节点不是当前节点的重节点并且也不是u的父节点,则将其的top设置成自己,进一步递归 144 dfs2(v,v);//每一个轻儿子都有一个从自己开始的链 145 } 146 } 147 148 void update_path(int x,int y,int k)//路径更新 149 { 150 k%=mod; 151 while(top[x]!=top[y]){ 152 if(dep[top[x]]<dep[top[y]]) swap(x,y);//使x深度较大 153 update(tid[top[x]],tid[x],k,1,n,1); 154 x=fa[top[x]]; 155 } 156 157 if(dep[x]>dep[y]) swap(x,y);//使x深度较小 158 update(tid[x],tid[y],k,1,n,1); 159 } 160 161 int getsum_path(int x,int y)//路径求和 162 { 163 int ans=0; 164 while(top[x]!=top[y]){//当两个点不在同一条链上 165 if(dep[top[x]]<dep[top[y]]) swap(x,y);//使x深度较大 166 ans+=query(tid[top[x]],tid[x],1,n,1),ans%=mod; 167 x=fa[top[x]];//x跳到x所在链顶端的这个点的上面一个点 168 } 169 170 if(dep[x]>dep[y]) swap(x,y);//当两个点处于同一条链,使x深度较小 171 ans+=query(tid[x],tid[y],1,n,1),ans%=mod; 172 return ans; 173 } 174 175 void update_subtree(int x,int k)//子树更新 176 { 177 update(tid[x],tid[x]+siz[x]-1,k,1,n,1);//子树区间右端点为tid[x]+siz[x]-1 178 } 179 180 int getsum_subtree(int x)//子树求和 181 { 182 return query(tid[x],tid[x]+siz[x]-1,1,n,1); 183 } 184 185 int main() 186 { 187 scanf("%d%d%d%d",&n,&m,&r,&mod); 188 init(); 189 for(int i=1;i<=n;i++) 190 scanf("%d",&w[i]);//点权 191 for(int i=1;i<=n-1;i++){ 192 int u,v; 193 scanf("%d%d",&u,&v); 194 add(u,v); 195 add(v,u); 196 } 197 dfs1(r,0);//根节点 198 dfs2(r,r); 199 build(1,n,1); 200 while(m--){ 201 int op,x,y,z; 202 scanf("%d",&op); 203 if(op==1){ 204 scanf("%d%d%d",&x,&y,&z); 205 update_path(x,y,z); 206 } 207 else if(op==2){ 208 scanf("%d%d",&x,&y); 209 printf("%d\n",getsum_path(x,y)); 210 } 211 else if(op==3){ 212 scanf("%d%d",&x,&z); 213 update_subtree(x,z); 214 } 215 else if(op==4){ 216 scanf("%d",&x); 217 printf("%d\n",getsum_subtree(x)); 218 } 219 } 220 return 0; 221 }
溜了溜了。。。