zoukankan      html  css  js  c++  java
  • 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

    好的说题解。

    可以发现(我并没发现),题中的两种操作都可以用矩阵来表示。假如用下面这个矩阵表示一个节点的状态

    [egin{bmatrix}1 & v & tend{bmatrix} ]

    那么操作1可以表示为

    [egin{bmatrix}1 & v & tend{bmatrix} *egin{bmatrix}1 & d & 0\0 & 1 & 0\0 & 0 & 1end{bmatrix}=egin{bmatrix}1 & v + d & tend{bmatrix} ]

    (可以看出那个1就是用来给v加上d的)
    然后操作2可以表示为

    [egin{bmatrix}1 & v & tend{bmatrix} *egin{bmatrix}1 & 0 & 0\0 & 1 & d\0 & 0 & 1end{bmatrix}=egin{bmatrix}1 & v & t + v * dend{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('
    ')
    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;
    }
    
  • 相关阅读:
    JavaScript 原型和原型链 prototype
    javascript dom 表单元素之 radio
    JavaScript Dom 表单元素之 checkbox
    JavaScript DOM 表单元素之 select
    JavaScript-ECMAScript 之模块
    Javascript--ECMAScript 之 this
    Javascript-ECMAscript--Array.prototype.slice() 方法
    JavaScript -ECMAScriopt: Array.prototype.slice.call()详解及转换数组的方法
    JavaScript-ECMASCript apply call bind
    requests的深入刨析及封装调用
  • 原文地址:https://www.cnblogs.com/RabbitHu/p/51nod1462.html
Copyright © 2011-2022 走看看