Luogu 3676 小清新数据结构题

推荐博客: http://www.cnblogs.com/Mychael/p/9257242.html

感觉还挺好玩的

首先考虑以1为根,把每一个点子树的权值和都算出来,记为$val_{i}$,那么在所有操作都没有开始的时候(以$1$为根的)$ans_{1} = \sum_{i= 1}^{n}val_{i}^{2}$

考虑到一个修改的操作只会对修改的点$x$到根($1$)链上的点产生影响,那么一次修改只要修对这条树链上的点增加$v - a_{x}$(假设修改后的值为$v$)就好了。

链剖之后线段树维护一下$val_{i}$,区间修改就很简单。

然后考虑换根:

我们发现当以$x$为根的时候,$x$原来的子树显然不会受到影响,而变化了的是原来的根$1$到$x$的链上的点,不妨设有$k$个结点,换根前(以$1$为根)的每个结点子树$val$值和为$a_{i}$,换根后(以$x$为根)的每个结点子树$val$值和为$b_{i}$

有一条显然的性质:$a_{i + 1} + b_{i} = a_{1} = b_{k}$都等于原来全部结点的$val$值和

那么换根之后的答案  $ans_{x} = ans_{1} - \sum_{i = 1}^{k}a_{i}^{2} + \sum_{i = 1}^{k}b_{i}^{2}$

代入上面的那条性质消掉$b$,发现$ans_{x} = ans_{1} + (k - 1)a_{1}^{2} - 2a_{1}\sum_{i = 2}^{k}a_{i}$

设$s_{i}$表示$i$的子树中所有$val$值和,那么$ans_{x} = ans_{1} + s_{1}((k + 1) s_{1} - 2\sum_{i = 1}^{k}s_{i})$。

容易发现这个$k$即为$dep_{x}$,而这个$\sum_{i = 1}^{k}s_{i}$ 和 $s_{1}$显然可以用线段树维护出来

考虑一下, 一次修改还会对$ans_{1}$产生影响,$ans_{1} += \sum_{i = 1}^{tot}(val_{i}+ \Delta v)^{2} - \sum_{i = 1}^{tot}val_{i}^{2} = tot\Delta v^{2} + 2\Delta v\sum_{i = 1}^{tot}val_{i}$。

因为每次发生变化的只有一条树链上的点,所以$tot = dep_{x}$,这个原来的$\sum_{i = 1}^{tot}val_{i}$可以在跳轻重链的过程中算出来。

时间复杂度$O(nlog^{2}n)$。

Code:

#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;

const int N = 2e5 + 5;

int n, qn, dfsc = 0, dep[N], siz[N], id[N];
int tot = 0, head[N], top[N], fa[N], son[N];
ll a[N], ans = 0LL, nowSum = 0LL, w[N], val[N];

struct Edge {
    int to, nxt;
} e[N << 1];

inline void add(int from, int to) {
    e[++tot].to = to;
    e[tot].nxt = head[from];
    head[from] = tot;
}

template <typename T>
inline void read(T &X) {
    X = 0;
    char ch = 0;
    T op = 1;
    for(; ch > ‘9‘|| ch < ‘0‘; ch = getchar())
        if(ch == ‘-‘) op = -1;
    for(; ch >= ‘0‘ && ch <= ‘9‘; ch = getchar())
        X = (X << 3) + (X << 1) + ch - 48;
    X *= op;
}

void dfs1(int x, int fat, int depth) {
    siz[x] = 1, fa[x] = fat, dep[x] = depth, val[x] = a[x];
    int maxson = -1;
    for(int i = head[x]; i; i = e[i].nxt) {
        int y = e[i].to;
        if(y == fat) continue;
        dfs1(y, x, depth + 1);
        siz[x] += siz[y], val[x] += val[y];
        if(siz[y] > maxson)
            maxson = siz[y], son[x] = y;
    }
}

void dfs2(int x, int topf) {
    w[id[x] = ++dfsc] = val[x], top[x] = topf;
    if(!son[x]) return;
    dfs2(son[x], topf);
    for(int i = head[x]; i; i = e[i].nxt) {
        int y = e[i].to;
        if(y == fa[x] || y == son[x]) continue;
        dfs2(y, y);
    }
}

namespace SegT {
    ll s[N << 2], tag[N << 2];

    #define lc p << 1
    #define rc p << 1 | 1
    #define mid ((l + r) >> 1)

    inline void up(int p) {
        if(p) s[p] = s[lc] + s[rc];
    }

    inline void down(int p, int l, int r) {
        if(!tag[p]) return;
        s[lc] += 1LL * (mid - l + 1) * tag[p];
        s[rc] += 1LL * (r - mid) * tag[p];
        tag[lc] += tag[p], tag[rc] += tag[p];
        tag[p] = 0LL;
    }

    void build(int p, int l, int r) {
        tag[p] = 0LL;
        if(l == r) {
            s[p] = w[l];
            return;
        }

        build(lc, l, mid);
        build(rc, mid + 1, r);
        up(p);
    }

    void modify(int p, int l, int r, int x, int y, ll v) {
        if(x <= l && y >= r) {
            s[p] += 1LL * (r - l + 1) * v;
            tag[p] += v;
            return;
        }

        down(p, l, r);
        if(x <= mid) modify(lc, l, mid, x, y, v);
        if(y > mid) modify(rc, mid + 1, r, x, y, v);
        up(p);
    }

    ll qSum(int p, int l, int r, int x, int y) {
        if(x <= l && y >= r) return s[p];
        down(p, l, r);

        ll res = 0LL;
        if(x <= mid) res += qSum(lc, l, mid, x, y);
        if(y > mid) res += qSum(rc, mid + 1, r, x, y);
        return res;
    }

} using namespace SegT;

inline void mTree(int x) {
    ll v, sum = 0LL, len = (ll)dep[x]; read(v);
    v -= a[x], a[x] += v;
    for(; x != 0; x = fa[top[x]]) {
        sum += qSum(1, 1, n, id[top[x]], id[x]);
        modify(1, 1, n, id[top[x]], id[x], v);
    }
    ans += 2LL * v * sum + 1LL * v * v * len;
    nowSum += v;
}

inline ll qTree(int x) {
    ll res = 0LL;
    for(; x != 0; x = fa[top[x]])
        res += qSum(1, 1, n, id[top[x]], id[x]);
    return res;
}

inline void solve(int x) {
    ll k = (ll)dep[x], sum = qTree(x);
    printf("%lld\n", ans + nowSum * ((k + 1) * nowSum - 2 * sum));
}

int main() {
    read(n), read(qn);
    for(int x, y, i = 1; i < n; i++) {
        read(x), read(y);
        add(x, y), add(y, x);
    }
    for(int i = 1; i <= n; i++) read(a[i]);

    dfs1(1, 0, 1);
    dfs2(1, 1);
    build(1, 1, n);

/*    for(int i = 1; i <= n; i++)
        printf("%d ", dep[i]);
    printf("\n");
    for(int i = 1; i <= n; i++)
        printf("%d ", top[i]);
    printf("\n");
    for(int i = 1; i <= n; i++)
        printf("%d ", w[i]);
    printf("\n");   */

    for(int i = 1; i <= n; i++)    {
        nowSum += a[i];
        ans += val[i] * val[i];
    }
//    printf("%lld\n", ans);

    for(int op, x; qn--; ) {
        read(op), read(x);
        if(op == 1) mTree(x);
        else solve(x);
    }

    return 0;
}

原文地址:https://www.cnblogs.com/CzxingcHen/p/9528795.html

时间: 2024-10-12 08:25:42

Luogu 3676 小清新数据结构题的相关文章

【luoguP3676】 小清新数据结构题 推式子(LCT维护)

挺有趣的一个数据结构题,不必用 LCT 维护,只不过 LCT 比较好写 ~ code: #include <cstdio> #include <string> #include <cstring> #include <algorithm> #define N 200008 #define ll long long #define lson s[x].ch[0] #define rson s[x].ch[1] using namespace std; ll a

[Luogu3676]小清新数据结构题

题面戳我 题意:给一棵树,树上有点权,每次操作为修改一个点的点权,或者是询问以某个点为根时,每棵子树(以每个点为根,就有n棵子树)点权和的平方和. \(n\le2*10^5\),保证答案在long long范围内 sol 我们设\(s_i\)表示以\(p\)为整棵树的根时,以\(i\)为根的子树的点权和.设\(Sum\)表示所有点的点权和,即\(Sum=\sum_{i=1}^{n}val_i\). 所以这道题给出\(p\),就是要你求\(\sum_{i=1}^{n}s_i^2\). 我们先看\(

Luogu3676 小清新数据结构题 动态点分治

传送门 换根类型的统计问题动态点分治都是很好做的. 设所有点的点权和为$sum$ 首先,我们先不考虑求$\sum\limits_i s_i^2$,先考虑如何在换根的情况下求$\sum\limits_i s_i$. 考虑一个点$i$会被统计多少次,显然是$dep_i+1$,那么$\sum\limits_i s_i = \sum\limits_i (dep_i+1) \times val_i = \sum\limits_i dep_i \times val_i + sum$. $\sum\limit

【刷题】洛谷 P3676 小清新数据结构题

题目背景 本题时限2s,内存限制256M 题目描述 在很久很久以前,有一棵n个点的树,每个点有一个点权. 现在有q次操作,每次操作是修改一个点的点权或指定一个点,询问以这个点为根时每棵子树点权和的平方和. (题目不是很好懂,没看太懂的可以看看样例解释) 输入输出格式 输入格式: 第一行两个整数n.q. 接下来n-1行每行两个整数a和b,表示树中a与b之间有一条边,保证给出的边不会重复. 接下来一行n个整数,第i个整数表示第i个点的点权. 接下来q行每行两或三个数,如果第一个数为1,那么接下来有两

lp3676 小清新数据结构题

传送门 Description 有一棵\(n\)个点的树,每个点有一个点权. 现在有\(q\)次操作,每次操作是修改一个点的点权或指定一个点,询问以这个点为根时每棵子树点权和的平方和. Solution 我们设\(Sum=\sum_{i=1}^{n} w_i\),\(s_i\)表示\(i\)子树的权值和 发现不管根是哪个节点,\(W=\sum_{i=1}^n s_i(Sum-s_i)\)都是一个定值 因为它相当于对于每条边连接的两个联通块的"点权和的积"的和 所以,我们要求的\(\su

小清新签到题(DP)

传送门 然鹅我并不觉得这道题很清新rua 思维巧妙!(参考) 对于第k小,我们可以这样考虑,若是第k小,那么比它小的方案应该是有k-1个. 在排列组合中,若固定i放在j位置,方案数是确定的,即:i固定在j位置,满足这个条件的序列的rank是在一个范围内的. 对于逆序对 常见思考方式是从小到大枚举数字,考虑对逆序对个数做出的贡献(从小到大插入,后插入不会对前插入造成影响) 设f[i][j]为i个数,j个逆序对的方案数,可得转移方程为 f[i][j]=f[i-1][j]+f[i-1][j-1]+……

【题解】Luogu P3674 小清新人渣的本愿

原题传送门 这题还算简单(我记得我刚学oi时就来写这题,然后暴力都爆零了) 看见无修改,那么这题应该是莫队 维护两个bitset,第二个是第一个的反串,bitset内维护每个数字是否出现过 第一种操作: 要求y-z=x,所以y=z+x 最后判断有没有k和k-x都出现在bitset中的情况 第二种操作: 和第一种类似的方法,就不再讲了qwqwq 第三种操作: 暴力把x分解成两个数的乘积,判断这两个数是否出现过 因为莫队是\(O(n \sqrt n)\)的·,所以这里也不需要更快 #include

绪论-第1章-《数据结构题集》习题解析-严蔚敏吴伟民版

习题集解析部分 第1章  绪论 ——<数据结构题集>-严蔚敏.吴伟民版        源码使用说明  链接??? <数据结构-C语言版>(严蔚敏,吴伟民版)课本源码+习题集解析使用说明        课本源码合辑  链接??? <数据结构>课本源码合辑        习题集全解析  链接??? <数据结构题集>习题解析合辑          本习题文档的存放目录:数据结构\▼配套习题解析\▼01 绪论\        文档中源码的存放目录:数据结构\▼配套习

查找-第9章-《数据结构题集》习题解析-严蔚敏吴伟民版

习题集解析部分 第9章 查找 ——<数据结构题集>-严蔚敏.吴伟民版        源码使用说明  链接??? <数据结构-C语言版>(严蔚敏,吴伟民版)课本源码+习题集解析使用说明        课本源码合辑  链接??? <数据结构>课本源码合辑        习题集全解析  链接??? <数据结构题集>习题解析合辑       相关测试数据下载  链接? 数据包       本习题文档的存放目录:数据结构\▼配套习题解析\▼09 查找       文档