首页 > 其他分享 >对树链剖分的爱 题解

对树链剖分的爱 题解

时间:2024-08-18 11:08:04浏览次数:10  
标签:x1 剖分 int 题解 x2 add 对树链 y1 y2

前言

题目链接:洛谷

题意简述

给出一棵 \(n\) 个节点以 \(1\) 为根的有根树。对于第 \(2\leq i\leq n\) 个节点,其父亲 \(f_i\) 在 \([l_i,r_i]\) 中均匀随机。每个树的边有边权,初始为 \(0\)。

现在有 \(m\) 次操作,第 \(i\) 次操作表示将 \((u_i,v_i)\) 的路径上所有的边的权值统一加上 \(w_i\)。\(m\) 次操作结束后,对于所有 \(i=2\sim n\),求 \((i,f_i)\) 边上权值的期望,对 \(998244353\) 取模。

\(1\leq l_i\leq r_i<i\),\(n \leq 5 \times 10^3\),\(m \leq 10^5\)。(原题 \(m \leq 5 \times 10^3\)。)

题目分析

这道题树的形态十分鬼畜,平常树上那一套都不管用了,于是变得无从下手。似乎能下手的只有随机等概率跳父亲这一操作。

修改之间独立,考虑某一次修改 \((u_i, v_i, w_i)\),我们要对 \(u_i \sim v_i\) 上,除了 \(\operatorname{lca}(u_i, v_i)\) 处的答案都加上 \(w_i\) 与这种树的形态的概率之积,即期望。联想到跳 \(\operatorname{lca}\) 的过程,在 \(u = v\) 之前,每次选取深度较深的结点,往上等概率地跳一次父亲。

对于本题,深度不确定,但是我们只用保证不会跳过 \(\operatorname{lca}\) 即可。什么情况下会跳过呢?就是 \(u\) 跳到了可能成为 \(\operatorname{lca}\) 的点,下一步不能让它往上跳。显然,可能成为 \(\operatorname{lca}\) 的点是 \(u\) 和 \(v\) 当中较小的一个。

即,假设 \(u < v\),\(ans_v \gets ans_v + w\),\(v \gets f_v \in [l_v, r_v]\),\(w \gets \cfrac{w}{r_v - l_v + 1}\)。

发现,一对 \((u, v)\) 会被多次访问,与其多次重复操作,不妨先将其记下来,然后一起往上更新。数据范围允许我们设 \(f_{u, v}\) 表示当前已经跳到了 \((u, v)\),对 \(u \sim v\) 这条树链修改的期望。类比上面的转移,有:

\[\forall k \in [l_v, r_v], f_{u, k} \gets f_{u, k} + \cfrac{f_{u, v}}{r_v - l_v + 1} \]

初始 \(f_{u_i, v_i} = w_i\)。我们要保证转移时刻有意义,即保证 \(u < v\)。上式若出现 \(k = u\) 的状态应舍弃;若出现 \(k < u\),要把 \(f_{u, k}\) 变成 \(f_{k, u}\)。由于修改独立,我们在 \(m\) 次操作后一起转移。

至此,时间复杂度是 \(\Theta(n^3 + m)\),考虑优化。

发现加上的数都是相同的,设 \(C = \cfrac{f_{u, v}}{r_v - l_v + 1}\),对于 \(k < u\) 的情况和 \(k > u\) 的情况分别拎出来观察:

\[\forall k \in [l_v, \min \lbrace r_v, u - 1 \rbrace], f_{k, u} \gets f_{k, u} + C \]

\[\forall k \in [\max \lbrace l_v, u + 1 \rbrace, r_v], f_{u, k} \gets f_{u, k} + C \]

很明显的区间加操作,使用二维前缀和维护即可。

时间复杂度:\(\Theta(n ^ 2 + m)\)。

代码

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;

const int N = 5020;
const int mod = 998244353;

inline int add(int a, int b) {
    return a + b >= mod ? a + b - mod : a + b;
}

inline int sub(int a, int b) {
    return a - b < 0 ? a - b + mod : a - b;
}

inline int mul(int a, int b) {
    return 1ll * a * b % mod;
}

int Inv[N];

int n, m, L[N], R[N];
int f[N][N];
int ans[N];

inline void add(int x1, int y1, int x2, int y2, int v) {
    if (x1 > x2 || y1 > y2) return;
    f[x2][y2] = add(f[x2][y2], v);
    f[x1 - 1][y1 - 1] = add(f[x1 - 1][y1 - 1], v);
    f[x1 - 1][y2] = sub(f[x1 - 1][y2], v);
    f[x2][y1 - 1] = sub(f[x2][y1 - 1], v);
}

signed main() {
    scanf("%d", &n);
    Inv[1] = 1;
    for (int i = 2; i <= n; ++i)
        Inv[i] = mul(mod - mod / i, Inv[mod % i]);
    for (int i = 2; i <= n; ++i)
        scanf("%d%d", &L[i], &R[i]);
    scanf("%d", &m);
    for (int _ = 1, u, v, w; _ <= m; ++_) {
        scanf("%d%d%d", &u, &v, &w);
        if (u == v) continue;
        if (u > v) swap(u, v);
        add(u, v, u, v, w);
    }
    for (int u = n; u >= 1; --u) {
        for (int v = n; v >= u; --v) {
            f[u][v] = add(f[u][v], f[u + 1][v]);
            f[u][v] = add(f[u][v], f[u][v + 1]);
            f[u][v] = sub(f[u][v], f[u + 1][v + 1]);
            if (v > u) {
                int val = mul(Inv[R[v] - L[v] + 1], f[u][v]);
                add(u, max(u + 1, L[v]), u, R[v], val);
                add(L[v], u, min(u - 1, R[v]), u, val);
                ans[v] = add(ans[v], f[u][v]);
            }
            // for (int o = max(u + 1, L[v]); o <= R[v]; ++o) {
            //     f[u][o] = add(f[u][o], mul(Inv[R[v] - L[v] + 1], f[u][v]));
            // }
            // for (int o = L[v]; o <= min(u - 1, R[v]); ++o) {
            //     f[o][u] = add(f[o][u], mul(Inv[R[v] - L[v] + 1], f[u][v]));
            // }
        }
    }
    for (int i = 2; i <= n; ++i)
        printf("%d ", ans[i]);
    return 0;
}

后记 & 反思

遇到期望 / 概率的题目,要往 DP 的方向思考,而不是像无头苍蝇一样只会写一个爆搜。

遇到宏观情况很鬼畜的问题,往往微观上一次操作是可控的,那么着眼于微观,每次迈出一小步即可。

标签:x1,剖分,int,题解,x2,add,对树链,y1,y2
From: https://www.cnblogs.com/XuYueming/p/18365079

相关文章

  • AtCoder Beginner Contest 367 题解(E~G)
    E转换关系看作有向边,\(n\)点\(n\)边构成基环树森林,基环树森林k后继唯一,记f[i][j]为点\(i\)的\(2^j\)级祖先,随便倍增。F一眼哈希,不知道有没有不哈希的做法。在这里我们不关心元素的顺序,只关心元素是否出现以及出现几次,考虑一个\(n\)位\(n+1\)进制数,\(a_i\)出现一......
  • [题解]CF76B Mice
    思路比较简单的贪心。对于可以选择两个奶酪的老鼠,我们先将它们忽略掉。现在所有老鼠所吃的奶酪是唯一确定的。考虑加上可以选择两个奶酪的老鼠如何选择。显然,如果它可以选择一个没有任何老鼠吃过的奶酪,它必然这样选择。其次,如果它可以选择的奶酪被吃掉的时间\(t\)与它到达奶......
  • ABC 367 题解
    AtCoderBeginnerContest367题解:\(Problem\hspace{2mm}A-Shout\hspace{2mm}Everyday\)题目链接opinion:~~code:#include<bits/stdc++.h>#definelllonglong#definepiipair<int,int>usingnamespacestd;lla,b,c;intmain(){ i......
  • abc364 题解
    第一次场切A~F,写个题解纪念一下。比赛链接:https://atcoder.jp/contests/abc367A-ShoutEveryday题意:高桥每天\(B\)时刻睡觉,\(C\)时刻起床(\(B,C\)时刻也在睡觉),请问高桥\(A\)时刻是否没睡。思路:对于\(B<=C\)和\(B>C\)分别处理即可。代码:https://atcoder.jp/con......
  • 题解:AtCoder Beginner Contest 367
    总体情况A题意在AtCoder王国,居民们每天都要在\(A\)点大声喊出他们对章鱼烧的热爱。住在AtCoder王国的高桥每天\(B\)点睡觉,\(C\)点起床(\(24\)小时钟)。他醒着的时候可以喊出对章鱼烧的爱,但睡着的时候却不能。判断他是否每天都能喊出对章鱼烧的爱。这里,一天有\(24......
  • P10886 【MX-S3-T2】「FeOI Round 1」Journey 题解
    我们肯定是要先求出来一个位置被加的次数,然后和权值乘一下就行。对于一个位置\(i\),右端点\(b\)肯定是随便选,一共\(n-i+1\)种情况。再是对于每一种步长\(c\),左端点\(a\)都有\(\left\lceil\dfrac{i}{c}\right\rceil\)种取值,暴力计算,时间复杂度\(O(n^2)\)。(提交记录)考虑......
  • P10888 【MX-S3-T4】「FeOI Round 1」醒餞の鳥 (feat. Feryquitous) 题解
    话说这题真的有紫吗(问号注意到数据范围中提到$\sum{nm}\le2\times10^5$,这里实际上是隐含了\(\min(n,m)\le\sqrt{2\times10^5}\)的。我们考虑根据\(n\)和\(m\)的大小关系设计出不同的算法。\(m<n\)一个比较直接的想法是对于每一个科目先按该科目的分数排序,这样......
  • 算法学习笔记之树链剖分
    算法学习笔记之(熟练跑分)树链剖分PART1首先是第一部份,也就是熟练跑分最最最基础的用法——求\(LCA\)首先是树链剖分//图片出自董晓算法大概就是这样本质就是根据子树大小将一颗树剖分成若干条链然后更加方便地处理/加速处理信息所以直接上代码?不,还要证明树链剖......
  • 在相思树下 III 题解
    前言题目链接:洛谷。赛时脑子坨成一坨了,估计是T1的影响,写一篇题解来理清思路。题意简述给你一个长为\(n\)的序列\(a_{1\dotsn}\),你需要对它进行两种操作共\(n-1\)次。对一个长度为\(l\)的序列\(b_{1\dotsl}\)进行一次操作将会把序列变为一个长为\(l-1\)的序列......
  • n1gr tS0i 题解
    前言题目链接:洛谷。想了一个小时,想到后只用\(1\)分钟过了的题。官方题解过于晦涩,看到一篇很清晰的题解,于是写题解以记之。终于遇到时间瓶颈在输入的题目。题意简述有一个长度为\(n\)的\(\tt01\)串\(S\),你要对\(S\)进行恰好\(n\)次操作。每次操作选择\(1\leq......