The 2021 ICPC Asia Nanjing Regional Contest E.Paimon Segment Tree 区间合并线段树/维护矩阵乘法

#程序员如何平衡日常编码工作与提升式学习?#

The 2021 ICPC Asia Nanjing Regional Contest E.Paimon Segment Tree 区间合并线段树/维护矩阵乘法
题目大意
给定长度为n nn的序列a 1 , a 2 , … , a n a_1,a_2, \dots, a_na 
1

 ,a 
2

 ,…,a 
n

 ,要求支持区间加操作,同时对操作记录历史版本,查询问区间[ l , r ] [l,r][l,r]操作[ s , t ] [s,t][s,t]中的每个数的平方之和。

题目思路
推了一会,发现线段树合并硬写很凌乱,然后队友告诉是线段树维护矩阵乘法,那么就考虑怎么维护:

设:

l e n = r − l + 1 len = r - l + 1len=r−l+1,区间长度
s u m 1 sum_1sum 
1

 ,区间和
s u m 2 sum_2sum 
2

 ,区间平方和
s u m 3 sum_3sum 
3

 ,区间历史平方和
考虑每次区间加v a l valval,那么考虑各个数字的变化:

对于区间和,变化量就是l e n × v a l len \times vallen×val

对于区间平方和:
( ( x 1 + v a l ) 2 + ( x 2 + v a l ) 2 + ⋯ + ( x n + v a l ) 2 ) − ( x 1 2 + x 2 2 + ⋯ + x n 2 ) = ( 2 × x 1 + v a l ) × ( x 1 − x 1 + v a l ) … ( 2 × x 1 + v a l ) × ( x 1 − x 1 + v a l ) = l e n × v a l 2 + 2 × v a l × s u m 1 ((x_1 + val)^2 + (x_2 + val)^2 + \dots + (x_n + val)^2) - (x_1^2 + x_2^2 + \dots + x_n^2) \\= (2 \times x_1 + val) \times (x_1 - x_1 + val) \dots (2 \times x_1 + val) \times (x_1 - x_1 + val)\\ =len \times val^2 + 2 \times val \times sum_1
((x 
1

 +val) 
2
 +(x 
2

 +val) 
2
 +⋯+(x 
n

 +val) 
2
 )−(x 
1
2

 +x 
2
2

 +⋯+x 
n
2

 )
=(2×x 
1

 +val)×(x 
1

 −x 
1

 +val)…(2×x 
1

 +val)×(x 
1

 −x 
1

 +val)
=len×val 
2
 +2×val×sum 
1

 

那么就有:

l e n = l e n len = lenlen=len
s u m 1 = s u m 1 + l e n × v a l sum_1 = sum_1 + len \times valsum 
1

 =sum 
1

 +len×val
s u m 2 = s u m 2 + 2 × v a l × s u m 1 + l e n × v a l 2 sum_2 = sum_2 + 2 \times val \times sum_1 + len \times val^2sum 
2

 =sum 
2

 +2×val×sum 
1

 +len×val 
2
 
s u m 3 = s u m 3 + s u m 2 + 2 × v a l × s u m 1 + l e n × v a l 2 sum_3 = sum_3 + sum_2 + 2 \times val \times sum_1 + len \times val^2sum 
3

 =sum 
3

 +sum 
2

 +2×val×sum 
1

 +len×val 
2
 
把上面的式子整理一下,就可以用矩阵乘法维护,转移矩阵:
[ 1 v a l v a l 2 v a l 2 0 1 2 × v a l 2 × v a l 0 0 1 1 0 0 0 1 ]
⎡⎣⎢⎢⎢1000val100val22×val10val22×val11⎤⎦⎥⎥⎥
[
1






2



2
0
1
2
×



2
×



0
0
1
1
0
0
0
1
]



  
1
0
0
0

  
val
1
0
0

  
val 
2
 
2×val
1
0

  
val 
2
 
2×val
1
1

  



 

看了看题解,说“设置了一个让不需要任何矩乘优化就可勉强通过的时限”,说明有卡常?

但并没有卡到(

Code

#include <bits/stdc++.h>
#pragma gcc optimize("O2")
#pragma g++ optimize("O2")
#define int long long
#define endl '\n'
using namespace std;

const int N = 1e5 + 10, MOD = 1e9 + 7;
int a[N], ans[N];

namespace ffastIO {
    const int bufl = 1 << 15;
    char buf[bufl], *s = buf, *t = buf;
    inline int fetch() {
        if (s == t) { t = (s = buf) + fread(buf, 1, bufl, stdin); if (s == t) return EOF; }
        return *s++;
    }
    inline int read() {
        int a = 0, b = 1, c = fetch();
        while (!isdigit(c))b ^= c == '-', c = fetch();
        while (isdigit(c)) a = a * 10 + c - 48, c = fetch();
        return b ? a : -a;
    }
}

using ffastIO::read;

struct Mat{
    static const int M = 4;
    int v[M][M];
    Mat() { memset(v ,0 ,sizeof(v)); }
    Mat(int k){
        v[0][0] = v[1][1] = v[2][2] = v[3][3] = v[2][3] = 1;
        v[0][1] = k;
        v[0][2] = v[0][3] = k * k % MOD;
        v[1][2] = v[1][3] = 2 * k % MOD;
        v[1][0] = v[2][0] = v[2][1] = v[3][0] = v[3][1] = v[3][2] = 0;
    }
    void eye() { for(int i = 0; i < M; i++) for(int j = 0; j < M; j++) v[i][j] = (i == j); }
    int * operator [] (int x) { return v[x]; }
    const int *operator [] (int x) const { return v[x]; }
    Mat operator * (const Mat& B) {
        const Mat &A = *this;
        Mat ret;
        for(int k = 0; k < M; k++){
            for(int i = 0; i < M; i++)
                for(int j = 0; j < M; j++){
                    ret[i][j] = (ret[i][j] + A[i][k] * B[k][j]) % MOD;
            }
        }
        return ret;
    }
    Mat operator + (const Mat& B) {
        const Mat &A = *this;
        Mat ret;
        for(int i = 0; i < M; i++)
            for(int j = 0; j < M; j++)
                ret[i][j] = (A[i][j] + B[i][j]) % MOD;
        return ret;
    }
    Mat pow(int n) const {
        Mat A = *this, ret; ret.eye();
        for(; n; n >>= 1, A = A * A) if(n & 1) ret = ret * A;
        return ret;
    }
};

namespace SegTree{
    #define ls rt << 1
    #define rs rt << 1 | 1
    #define lson ls, l, mid
    #define rson rs, mid + 1, r
    struct Info{
        int val[4];
        Info () {}
        Info (int x){ val[0] = 1, val[1] = x, val[2] = val[3] = x * x % MOD; }
        Info operator+ (Info b){ 
            Info res;
            for(int i = 0; i < 4; i++){ res.val[i] = (val[i] + b.val[i]) % MOD; } 
            return res; 
        }
        Info operator* (Mat b){
            int res[4] = {0, 0, 0, 0};
            for(int i = 0; i < 4; i++){
                for(int j = 0; j < 4; j++) res[j] = (res[j] + val[i] * b[i][j]) % MOD;
            }
            for(int i = 0; i < 4; i++) val[i] = res[i];
            return *this;
        }
    } tree[N << 2];

    Mat lazy[N << 2];

    inline void push_up(int rt){ tree[rt] = tree[ls] + tree[rs]; }

    inline void push(int rt, Mat k){ tree[rt] = tree[rt] * k, lazy[rt] = lazy[rt] * k; }

    inline void push_down(int rt){
        push(ls, lazy[rt]), push(rs, lazy[rt]);
        lazy[rt].eye();
    }

    void build(int rt, int l, int r){
        lazy[rt].eye();
        if(l == r) return (void)(tree[rt] = Info(a[l]));
        int mid = l + r >> 1;
        build(lson), build(rson);
        push_up(rt);
    }

    void update(int rt, int l, int r, int L, int R, int val){
        if(r < L || l > R || (l >= L && r <= R)) return push(rt, Mat((l >= L && r <= R) * val));
        push_down(rt);
        int mid = l + r >> 1;
        update(lson, L, R, val), update(rson, L, R, val);
        push_up(rt);
    }

    Info query(int rt, int l, int r, int L, int R){
        if(l >= L && r <= R) return tree[rt];
        push_down(rt);
        int mid = l + r >> 1;
        if(mid >= L && mid < R) return query(lson, L, R) + query(rson, L, R);
        if(mid >= L) return query(lson, L, R);
        if(mid < R) return query(rson, L, R);
        return Info(1);
    }
}

struct modify{ int l, r, val; }op[N];
struct query{ 
    int op, id, x, l, r;
    const bool operator< (const query &s) const { return x < s.x; } 
}que[N];

#define SEGRG 1, 1, n

inline void solve(){
    int n = read(), m = read(), q = read();
    for(int i = 1; i <= n; i++) a[i] = (read() + MOD) % MOD;
    SegTree::build(1, 1, n);
    for(int i = 1; i <= m; i++) op[i] = {read(), read(), (read() + MOD) % MOD};
    for(int i = 1; i <= q; i++){
        int l = read(), r = read(), x = read(), y = read();
        que[i * 2 - 1] = query{-1, i, x - 1, l, r};
        que[i * 2] = query{1, i, y, l, r};
    }
    sort(que + 1, que + 1 + 2 * q);
    int now = 1; // while(now <= 2 * q && que[now].x == -1) now++;
    for(; now <= 2 * q && que[now].x == -1; now++);
    for(int i = 0; i <= m; i++){
        if(i) SegTree::update(SEGRG, op[i].l, op[i].r, op[i].val);
        for(; now <= 2 * q && que[now].x == i; now++){
            ans[que[now].id] += que[now].op * SegTree::query(SEGRG, que[now].l, que[now].r).val[3] % MOD;
        }
            
    }
    for(int i = 1; i <= q; i++) printf("%d\n", (ans[i] + MOD) % MOD);
}


signed main(){
    solve();
    return 0;
}
点个关注不迷路

评论一下也行,我的文章到现在没一个评论。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值