P3384——树链剖分&&模板
2024-10-06 18:50:37
题目描述
如题,已知一棵包含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为根节点的子树内所有节点值之和
解决方法
用链式前向星的方式保存树,两次DFS将树剖分成若干重链和轻链,套用线段树进行更新和查询,对子树的操作可以转化成连续节点间的操作(因为DFS时子树节点的编号也是连续的),注意取模和开$long \ \ long$.
而且单独$add$标记时是不用下推的,只需查询时累加即可(不知道为什么那些题解都用下推的)
#include<bits/stdc++.h>
using namespace std; typedef long long ll;
#define lc o <<1
#define rc o <<1 | 1
//const ll INF = 0x3f3f3f3f;
const int maxn = + ;
struct Edge
{
int to, next;
}edges[*maxn];
int head[maxn];
int cur, f[maxn], deep[maxn], size[maxn], son[maxn], rk[maxn], id[maxn], top[maxn], cnt;
int n, q, w[maxn], root, mod; inline void addedge(int u, int v)
{
++cur;
edges[cur].next = head[u];
head[u] = cur;
edges[cur].to = v;
} struct SegTree{
ll sum[maxn << ], addv[maxn << ];
void build(int o, int l, int r)
{
if(l == r)
{
sum[o] = w[rk[l]] % mod;
}
else
{
int mid = (l + r) >> ;
build(lc, l, mid);
build(rc, mid+, r);
sum[o] = (sum[lc] + sum[rc]) % mod;
}
} void maintain(int o, int l, int r)
{
if(l == r) //如果是叶子结点
sum[o] = w[rk[l]] % mod;
else //如果是非叶子结点
sum[o] = (sum[lc] + sum[rc]) % mod; sum[o] = (sum[o] + addv[o] * (r-l+)) % mod;
}
//区间修改,[cl,cr] += v;
void update(int o, int l, int r, int cl, int cr, int v) //
{
if(cl <= l && r <= cr) addv[o] = (addv[o] + v) % mod;
else
{
int m = l + (r-l) /;
if(cl <= m) update(lc, l, m, cl, cr, v);
if(cr > m) update(rc, m+, r, cl, cr, v);
}
maintain(o, l, r);
} //区间查询,sum{ql,qr}
ll query(int o, int l,int r, ll add, int ql, int qr)
{
if(ql <= l && r <= qr)
{
//prllf("sum[o]:%d %d*(%d-%d+1)\n", sum[o], add, r, l);
return (sum[o] + add * (r-l+)) % mod; //tx l-r+1
}
else
{
int m = l + (r - l) / ;
ll ans = ;
add = (add + addv[o]) % mod;
if(ql <= m) ans = (ans + query(lc, l, m, add, ql, qr)) % mod;
if(qr > m) ans = (ans + query(rc, m+, r, add, ql, qr)) % mod;
return ans;
}
}
}st; void dfs1(int u, int fa, int depth) //当前节点、父节点、层次深度
{
//prllf("u:%d fa:%d depth:%d\n", u, fa, depth);
f[u] = fa;
deep[u] = depth;
size[u] = ; //这个点本身的size
for(int i = head[u];i;i = edges[i].next)
{
int v = edges[i].to;
if(v == fa) continue;
dfs1(v, u, depth+);
size[u] += size[v]; //子节点的size已被处理,用它来更新父节点的size
if(size[v] > size[son[u]]) son[u] = v; //选取size最大的作为重儿子
}
} void dfs2(int u, int t) //当前节点、重链顶端
{
//prllf("u:%d t:%d\n", u, t);
top[u] = t;
id[u] = ++cnt; //标记dfs序
rk[cnt] = u; //序号cnt对应节点u
if(!son[u]) return; //没有儿子?
dfs2(son[u], t); //我们选择优先进入重儿子来保证一条重链上各个节点dfs序连续 for(int i = head[u];i;i = edges[i].next)
{
int v = edges[i].to;
if(v != son[u] && v != f[u]) dfs2(v, v); //这个点位于轻链顶端,那么它的top必然为它本身
}
} /*修改和查询的原理是一致的,以查询操作为例,其实就是个LCA,不过这里要使用top数组加速,因为top可以直接跳到该重链的起始顶点*/
/*注意,每次循环只能跳一次,并且让结点深的那个跳到top的位置,避免两者一起跳而插肩而过*/
ll querysum(int x, int y)
{
int fx = top[x], fy = top[y];
ll ans = ;
while(fx != fy) //当两者不在同一条重链上
{
if(deep[fx] >= deep[fy])
{
//prllf("%d %d\n", id[fx], id[x]);
ans = (ans + st.query(, , n, , id[fx], id[x])) % mod; //线段树区间求和,计算这条重链的贡献
x = f[fx]; fx = top[x];
}
else
{
//prllf("%d %d\n", id[fy], id[y]);
ans = (ans + st.query(, , n, , id[fy], id[y])) % mod;
y = f[fy]; fy = top[y];
}
} //循环结束,两点位于同一重链上,但两者不一定为同一点,所以还要加上这两点之间的贡献
if(id[x] <= id[y])
{
//prllf("%d %d\n", id[x], id[y]);
ans = (ans + st.query(, , n, , id[x], id[y])) % mod;
}
else
{
//prllf("%d %d\n", id[y], id[x]);
ans = (ans + st.query(, , n, , id[y], id[x])) % mod;
}
return ans;
} void update_add(int x, int y, int add)
{
int fx = top[x], fy = top[y];
while(fx != fy) //当两者不在同一条重链上
{
if(deep[fx] >= deep[fy])
{
st.update(, , n, id[fx], id[x], add);
x = f[fx]; fx = top[x];
}
else
{
st.update(, , n, id[fy], id[y], add);
y = f[fy]; fy = top[y];
}
}
//循环结束,两点位于同一重链上,但两者不一定为同一点,所以还要加上这两点之间的贡献
if(id[x] <= id[y]) st.update(, , n, id[x], id[y], add);
else st.update(, , n, id[y], id[x], add);
} int main()
{
scanf("%d%d%d%d", &n, &q, &root, &mod);
for(int i = ;i <= n;i++)
{
scanf("%d", &w[i]);
w[i] %= mod;
}
for(int i = ;i < n;i++)
{
int u, v;
scanf("%d%d", &u, &v);
addedge(u, v);
addedge(v, u);
}
dfs1(root, -, );
dfs2(root, root); // for(ll i = 1;i <= n;i++) prllf("%d ", id[i]);
// prllf("\n");
// for(ll i = 1;i <= n;i++) prllf("%d ", rk[i]);
// prllf("\n"); st.build(, , n);
//scanf("%d", &q);
while(q--)
{
int op;
scanf("%d", &op);
if(op == )
{
int u, v, add;
scanf("%d%d%d", &u, &v, &add);
update_add(u, v, add);
}
else if(op == )
{
int u, v;
scanf("%d%d", &u, &v);
printf("%lld\n", querysum(u, v));
}
else if(op == )
{
int u, add;
scanf("%d%d", &u, &add);
st.update(, , n, id[u], id[u]+size[u]-, add);
}
else
{
int u;
scanf("%d", &u);
printf("%lld\n",st.query(, , n, , id[u], id[u]+size[u]-));
}
//st.prll_debug(1, 1, n);
}
}
最新文章
- Redis中connect与pconnect区别?
- maven建立本地仓库
- Android 图片圆角的设置
- codeforces 360 C - NP-Hard Problem
- HttpLib - 一个对 Http 协议进行封装的库
- eclipse开发web应用程序步骤(图解)
- 【HDOJ】2159 FATE
- css3 样式 圆角
- poj1265&;&;2954 [皮克定理 格点多边形]【学习笔记】
- 2.1JAVA基础复习——JAVA语言的基础组成注释和常量变量
- sqlserver 存储过程返回游标的处理
- strace命令用法
- 安装OpenResty开发环境
- 能用HTML/CSS解决的问题,就不要用JS
- 如何在windows下使用pip安装
- oracle 监听报错the information provided for this listener is currently in use by other software on this computer
- iOS 导航栏返回到指定页面的方法和理解
- NOIP2017 游记
- vue.js学习之 跨域请求代理与axios传参
- lintcode-167-链表求和