51nod 1462 树据结构 | 树链剖分 矩阵乘法

题目链接

51nod 1462

题目描述

给一颗以1为根的树。
每个点有两个权值:vi, ti,一开始全部是零。
Q次操作:
读入o, u, d
o = 1 对u到根上所有点的vi += d
o = 2 对u到根上所有点的ti += vi * d
最后,输出每个点的ti值(n, Q <= 100000)
有50%的数据N,Q <= 10000
注:所有数64位整数不会爆。

题解

这道题好神奇啊……看讨论版里的 AntiLeaf 大神的矩阵乘法打标记才找到思路,然后又看到 ccz181078 的评论,发现常数可以优化到这么小……真是太神了!一月份听尹涵学姐提到过矩阵乘法结合线段树,今天终于做到这样的题了2333

好的说题解。

可以发现(我并没发现),题中的两种操作都可以用矩阵来表示。假如用下面这个矩阵表示一个节点的状态
\[\begin{bmatrix}1 & v & t\end{bmatrix} \]
那么操作1可以表示为
\[\begin{bmatrix}1 & v & t\end{bmatrix} *\begin{bmatrix}1 & d & 0\\0 & 1 & 0\\0 & 0 & 1\end{bmatrix}=\begin{bmatrix}1 & v + d & t\end{bmatrix} \]
(可以看出那个1就是用来给v加上d的)
然后操作2可以表示为
\[\begin{bmatrix}1 & v & t\end{bmatrix} *\begin{bmatrix}1 & 0 & 0\\0 & 1 & d\\0 & 0 & 1\end{bmatrix}=\begin{bmatrix}1 & v & t + v * d\end{bmatrix} \]
这样两种操作就都可以通过矩阵来做啦。

然后根据题目要求显然需要树链剖分,线段树上维护矩阵就可以了。矩阵乘法满足结合律,所以可以正常地下放lazy标记。

但是这个矩阵乘法常数比较大(矩阵乘法\(O(n^3)\),\(n^3 = 27\))。但是这个矩阵比较特殊,它左下角三个位置恒为0,主对角线恒为1,于是并不需要\(O(n^3)\)地做矩阵乘法,实际上只需要4次加法和1次乘法,这是一个显著的常数优化!

代码:

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <vector>
#define space putchar(' ')
#define enter putchar('\n')
typedef long long ll;
using namespace std;
template <class T>
void read(T &x){
    char c;
    bool op = 0;
    while(c = getchar(), c < '0' || c > '9')
    if(c == '-') op = 1;
    x = c - '0';
    while(c = getchar(), c >= '0' && c <= '9')
    x = x * 10 + c - '0';
    if(op) x = -x;
}
template <class T>
void write(T x){
    if(x < 0) putchar('-'), x = -x;
    if(x >= 10) write(x / 10);
    putchar('0' + x % 10);
}

const int N = 100005;
int n, m, adj[N], nxt[N];
int fa[N], son[N], sze[N], top[N], pos[N], idx[N], tot;
ll ans[N];

struct matrix {
    ll g[3][3];
    matrix(){
    memset(g, 0, sizeof(g));
    }
    matrix(int x){
    memset(g, 0, sizeof(g));
    for(int i = 0; i < 3; i++)
        g[i][i] = 1;
    }
    bool empty(){
    return !g[0][1] && !g[0][2] && !g[1][2];
    }
    void clear(){
    g[0][1] = g[0][2] = g[1][2] = 0;
    }
    friend matrix opt_multi(const matrix &a, const matrix &b){
    matrix c(1);
    c.g[0][1] = a.g[0][1] + b.g[0][1];
    c.g[1][2] = a.g[1][2] + b.g[1][2];
    c.g[0][2] = a.g[0][2] + b.g[0][2] + a.g[0][1] * b.g[1][2];
    return c;
    }
} lazy[4*N];

void pushdown(int k){
    lazy[k << 1] = opt_multi(lazy[k << 1], lazy[k]);
    lazy[k << 1 | 1] = opt_multi(lazy[k << 1 | 1], lazy[k]);
    lazy[k].clear();
}
void change(int k, int l, int r, int ql, int qr, const matrix &x){
    if(ql <= l && qr >= r) return (void)(lazy[k] = opt_multi(lazy[k], x));
    if(!lazy[k].empty()) pushdown(k);
    int mid = (l + r) >> 1;
    if(ql <= mid) change(k << 1, l, mid, ql, qr, x);
    if(qr > mid) change(k << 1 | 1, mid + 1, r, ql, qr, x);
}
void pushdown_all(int k, int l, int r){
    if(l == r) return (void)(ans[idx[l]] = lazy[k].g[0][2]);
    if(!lazy[k].empty()) pushdown(k);
    int mid = (l + r) >> 1;
    pushdown_all(k << 1, l, mid);
    pushdown_all(k << 1 | 1, mid + 1, r);
}
void add(int u, int v){
    nxt[v] = adj[u];
    adj[u] = v;
}
void bfs(){
    static int que[N], qr;
    que[qr = 1] = 1;
    for(int ql = 1; ql <= qr; ql++)
    for(int u = que[ql], v = adj[u]; v; v = nxt[v])
        que[++qr] = v;
    for(int ql = qr, u; ql; ql--){
    u = que[ql];
    sze[fa[u]] += ++sze[u];
    if(sze[u] > sze[son[fa[u]]]) son[fa[u]] = u;
    }
    for(int ql = 1, u; ql <= qr; ql++)
    if(!top[u = que[ql]])
        for(int v = u; v; v = son[v])
        top[v] = u, idx[pos[v] = ++tot] = v;
}
void path_change(int o, int u, ll d){
    matrix x(1);
    (o == 1 ? x.g[0][1] : x.g[1][2]) = d;
    while(u){
    change(1, 1, n, pos[top[u]], pos[u], x);
    u = fa[top[u]];
    }
}

int main(){

    read(n);
    for(int i = 2; i <= n; i++)
    read(fa[i]), add(fa[i], i);
    bfs();
    read(m);
    while(m--){
    int o, u;
    ll d;
    read(o), read(u), read(d);
    path_change(o, u, d);
    }
    pushdown_all(1, 1, n);
    for(int i = 1; i <= n; i++)
    write(ans[i]), enter;

    return 0;
}

原文地址:https://www.cnblogs.com/RabbitHu/p/51nod1462.html

时间: 2024-10-12 22:29:17

51nod 1462 树据结构 | 树链剖分 矩阵乘法的相关文章

树的统计Count---树链剖分

NEFU专项训练十和十一——树链剖分 Description 一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w.我们将以下面的形式来要求你对这棵树完成一些操作: I. CHANGE u t : 把结点u的权值改为t II. QMAX u v: 询问从点u到点v的路径上的节点的最大权值 III. QSUM u v: 询问从点u到点v的路径上的节点的权值和 注意:从点u到点v的路径上的节点包括u和v本身 Input 输入的第一行为一个整数n,表示节点的个数.接下来n – 1行,每行2个整

kyeremal-bzoj1036[ZJOI2008]-树的统计count-树链剖分

bzoj1036[ZJOI2008]-树的统计count 1036: [ZJOI2008]树的统计Count Time Limit: 10 Sec  Memory Limit: 162 MB Submit: 7567  Solved: 3109 [Submit][Status][Discuss] Description 一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w.我们将以下面的形式来要求你对这棵树完成一些操作: I. CHANGE u t : 把结点u的权值改为t II. QM

洛谷 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,分别表示树的结点个数.操作个数

luogu3384 【模板】树链剖分

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,

树链剖分小结

这两周在学树剖. 先扔个模板 有一类题目,要求实现一类在树上的操作,比如: 修改/求 树上某 节点/边权 的(最)值: 修改/求 树上某 节点/边权 及其子树上所有节点的(最)值: 修改/求 树上某两点路径间的 节点/边权 的(最)值: 乍一看似乎用线段树就可以实现,但是如果仔细想想,可以发现单凭线段树是无法解决的. 对于这类题目,常用的解决方法是树链剖分. 树链剖分(节选自starszys博客): 相关定义: 重儿子:siz[u]为v的子节点中siz值最大的,那么u就是v的重儿子. 轻儿子:v

数据结构之树链剖分

首先了解一下基本概念: 重儿子:siz[u]为v的子节点中siz值最大的,那么u就是v的重儿子.      轻儿子:v的其它子节点.      重边:点v与其重儿子的连边.      轻边:点v与其轻儿子的连边.      重链:由重边连成的路径.      轻链:轻边. 剖分后的树有如下性质:      性质1:如果(v,u)为轻边,则siz[u] * 2 < siz[v]:      性质2:从根到某一点的路径上轻链.重链的个数都不大于logN. 树链剖分,如其字面意思,就是将一棵树按照轻重

codevs 1228 苹果树 树链剖分讲解

题目:codevs 1228 苹果树 链接:http://codevs.cn/problem/1228/ 看了这么多树链剖分的解释,几个小时后总算把树链剖分弄懂了. 树链剖分的功能:快速修改,查询树上的路径. 比如一颗树 首先,我们要把树剖分成树链.定义: fa[x]是x节点的上一层节点(就是他的爸爸). deep[x]是x节点的深度. num[x]是x节点下面的子节点的数量(包括自己) son[x]重儿子:一个节点的儿子的num[x]值最大的节点.其他的儿子都是轻儿子. 重链:重儿子连接在一起

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,

BZOJ 1146: [CTSC2008]网络管理Network( 树链剖分 + 树状数组套主席树 )

树链剖分完就成了一道主席树裸题了, 每次树链剖分找出相应区间然后用BIT+(可持久化)权值线段树就可以完成计数. 但是空间问题很严重....在修改时不必要的就不要新建, 直接修改原来的..详见代码. 时间复杂度O(N*log^3(N)) ---------------------------------------------------------------------------- #include<cstdio> #include<cstring> #include<