题目描述
给定一棵 $n$ 个点的树,每个点有点权,进行 $m$ 次操作
操作1:查询 $a - b$ 路径上最大字段和(可以为空)
操作2:把 $a - b$ 路径上的所有点的点权修改为 $c$
$n, m \leq 10^5$
输入样例
5
-3 -2 1 2 3
1 2
2 3
1 4
4 5
3
1 2 5
2 3 4 2
1 2 5
输出样例
5
9
树链剖分 线段树
其实只是把这个 最大子段和 的题目放在了树上,套一层树剖即可
不过有些细节需要注意:
-
题目中区间赋值的范围为 $[-10000, 10000]$ 所以无懒标记时 $lazy$ 应是一个这个区间之外的数
-
求路径上的最大子段和时,从两头向上跳的过程中,在线段树中的下标都是从大到小的,所以新查找的区间作为左子区间 $pushup$ 即可,但在最后把两段拼在一起时需要考虑拼接的方向
时间复杂度 $O(mlog^2n)$
C++ 代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 100010, M = N << 1, INF = 0x3f3f3f3f;
int h[N], e[M], ne[M], idx;
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
int a[N];
int sz[N], dep[N], son[N], f[N];
void dfs(int u, int fa)
{
sz[u] = 1, dep[u] = dep[fa] + 1, f[u] = fa;
for(int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if(j == fa) continue;
dfs(j, u);
sz[u] += sz[j];
if(sz[j] > sz[son[u]]) son[u] = j;
}
}
int id[N], rk[N], top[N], tot;
void dfs2(int u, int t)
{
id[u] = ++ tot; rk[tot] = u, top[u] = t;
if(son[u]) dfs2(son[u], t);
for(int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if(j == f[u] or j == son[u]) continue;
dfs2(j, j);
}
}
struct Node{
int l, r;
int d, ld, rd, sum, lazy;
}tr[N << 2];
void pushup(Node &u, Node left, Node right)
{
u.sum = left.sum + right.sum;
u.ld = max(left.ld, left.sum + right.ld);
u.rd = max(right.rd, right.sum + left.rd);
u.d = max(max(left.d, right.d), left.rd + right.ld);
}
void pushup(int u)
{
pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
}
void build(int u, int l, int r)
{
tr[u] = {l, r};
tr[u].lazy = INF;
if(l == r)
{
tr[u].sum = a[rk[l]];
tr[u].d = tr[u].ld = tr[u].rd = max(0, a[rk[l]]);
return;
}
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
pushup(u);
}
void pushdown(int u)
{
if(tr[u].lazy != INF)
{
Node &left = tr[u << 1], &right = tr[u << 1 | 1];
left.sum = tr[u].lazy * (left.r - left.l + 1);
left.d = left.ld = left.rd = max(0, left.sum);
right.sum = tr[u].lazy * (right.r - right.l + 1);
right.d = right.ld = right.rd = max(0, right.sum);
left.lazy = right.lazy = tr[u].lazy;
tr[u].lazy = INF;
}
}
void modify(int u, int l, int r, int c)
{
if(tr[u].l >= l and tr[u].r <= r)
{
tr[u].sum = c * (tr[u].r - tr[u].l + 1);
tr[u].d = tr[u].ld = tr[u].rd = max(0, tr[u].sum);
tr[u].lazy = c;
return;
}
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) modify(u << 1, l, r, c);
if(r > mid) modify(u << 1 | 1, l, r, c);
pushup(u);
}
void update(int a, int b, int c)
{
while(top[a] != top[b])
{
if(dep[top[a]] < dep[top[b]]) swap(a, b);
modify(1, id[top[a]], id[a], c);
a = f[top[a]];
}
if(dep[a] > dep[b]) swap(a, b);
modify(1, id[a], id[b], c);
}
Node query(int u, int l, int r)
{
if(tr[u].l >= l and tr[u].r <= r) return tr[u];
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(r <= mid) return query(u << 1, l, r);
if(l > mid) return query(u << 1 | 1, l, r);
Node left = query(u << 1, l, r), right = query(u << 1 | 1, l, r), res;
pushup(res, left, right);
pushup(u);
return res;
}
int ask(int a, int b)
{
Node res = {0, 0, 0, 0, 0, 0, 0};
Node left = res, right = res;
while(top[a] != top[b])
{
if(dep[top[a]] > dep[top[b]])
{
Node q = query(1, id[top[a]], id[a]);
pushup(left, q, left);
a = f[top[a]];
}
else
{
Node q = query(1, id[top[b]], id[b]);
pushup(right, q, right);
b = f[top[b]];
}
}
if(dep[a] > dep[b]) swap(a, b), swap(left, right);
swap(left.ld, left.rd);
Node q = query(1, id[a], id[b]);
pushup(left, left, q);
pushup(res, left, right);
return res.d;
}
int main()
{
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
memset(h, -1, sizeof h);
for(int i = 1; i < n; i ++)
{
int a, b;
scanf("%d%d", &a, &b);
add(a, b), add(b, a);
}
dfs(1, 0);
dfs2(1, 1);
build(1, 1, n);
//for(int i = 1; i <= n; i ++) printf("query(%d) = %d\n", i, query(1, i, i).d);
//for(int i = 1; i <= n; i ++) printf("id[%d] = %d top[%d] = %d\n", i, id[i], i, top[i]);
int m;
scanf("%d", &m);
while(m --)
{
int op, a, b, c;
scanf("%d%d%d", &op, &a, &b);
if(op == 1)
{
printf("%d\n", ask(a, b));
}
else
{
scanf("%d", &c);
update(a, b, c);
}
}
return 0;
}
附赠一个调试用的树
8
1 2 3 4 5 6 7 8
1 2
1 7
2 3
2 6
3 5
3 4
7 8
0