SP1716 GSS3 - Can you answer these queries III - 动态dp,线段树

GSS3

Description

动态维护最大子段和,支持单点修改。

Solution

设 \(f[i]\) 表示以 \(i\) 为结尾的最大子段和, \(g[i]\) 表示 \(1 \sim i\) 的最大子段和,那么

\[f[i] = max(f[i - 1] + a[i], a[i])\]

\[g[i] = max(g[i - 1], f[i])\]

发现只跟前一项有关。我们希望使用矩阵乘法的思路,但是矩阵乘法通常只能适用于递推问题。因此我们引入广义矩阵乘法。

矩阵乘法问题可分治的原因在于矩阵乘法满足结合律,而满足结合律的根本原因是乘法对加法满足分配率,即

\[a\cdot (b+c) = a\cdot c + b\cdot c\]

那么在这里,很容易发现,加法运算对\(Min/Max\)运算也是满足分配率的,即

\[a + min(b,c) = min(a+c,b+c)\]
\[a + max(b,c) = max(a+c,b+c)\]

所谓广义矩阵乘法,就是将矩阵乘法中的加法运算换成\(Min/Max\)运算,乘法运算换成加法运算,那么这样的矩阵乘法仍然满足结合律。

考虑到 \(g[i]\) 从 \(f[i]\) 转移过来的那一项可以直接拆开,很容易得到转移方程

\[
\begin{bmatrix}
f_{i} \\ g_{i} \\ 0
\end{bmatrix}
=
\begin{bmatrix}
a_{i} & -\infty & a_{i} \\
a_{i} & 0 & a_{i}\\
-\infty & -\infty & 0 \\
\end{bmatrix}
\cdot
\begin{bmatrix}
f_{i-1} \\ g_{i-1} \\ {0}
\end{bmatrix}
\]

可以将其记为

\[F_i = A_i \cdot F_{i-1}\]

于是我们用线段树暴力维护所有\(A_i\)的乘积即可。复杂度\(O(27n \log{n})\)

#include <bits/stdc++.h>
using namespace std;
#define int long long
struct Matrix {
    int n,m,a[5][5];
    Matrix() {
        n=m=0;
        for(int i=0;i<4;i++) for(int j=0;j<4;j++) a[i][j]=0;
    }
    Matrix operator * (const Matrix &y) {
        Matrix r;
        if(m!=y.n) return r;
        r.n = n; r.m = y.m;
        for(int i=1;i<=n;i++) {
            for(int j=1;j<=y.m;j++) {
                for(int k=1;k<=m;k++) {
                    if(k==1) r.a[i][j]=a[i][k]+y.a[k][j];
                    else r.a[i][j]=max(r.a[i][j],a[i][k]+y.a[k][j]);
                }
            }
        }
        return r;
    }
};
Matrix make(int x) {
    Matrix r;
    r.m=r.n=3;
    r.a[1][1]=r.a[1][3]=r.a[2][1]=r.a[2][3]=x;
    r.a[2][2]=r.a[3][3]=0;
    r.a[1][2]=r.a[3][1]=r.a[3][2]=-1e+9;
    return r;
}

const int N = 1000005;

Matrix val[N],zero;
int n,q,src[N],t1,t2,t3;

void pushup(int p) {
    val[p] = val[p*2]*val[p*2+1];
}
void build(int p,int l,int r) {
    if(l==r) {
        val[p]=make(src[l]);
    }
    else {
        build(p*2,l,(l+r)/2);
        build(p*2+1,(l+r)/2+1,r);
        pushup(p);
    }
}
void modify(int p,int l,int r,int pos,int key) {
    if(l==r) {
        val[p]=make(key);
    }
    else {
        if(pos<=(l+r)/2) modify(p*2,l,(l+r)/2,pos,key);
        else modify(p*2+1,(l+r)/2+1,r,pos,key);
        pushup(p);
    }
}
Matrix query(int p,int l,int r,int ql,int qr) {
    Matrix R=make(-1e+9);
    if(l>qr||r<ql) return R;
    if(l>=ql&&r<=qr) return val[p];
    return query(p*2,l,(l+r)/2,ql,qr)*query(p*2+1,(l+r)/2+1,r,ql,qr);
}

signed main() {
    ios::sync_with_stdio(false);
    cin>>n;
    for(int i=1;i<=n;i++) cin>>src[i];
    cin>>q;
    zero.n=3; zero.m=1;
    zero.a[1][1]=zero.a[2][1]=-1e+9;
    build(1,1,n);
    for(int i=1;i<=q;i++) {
        cin>>t1>>t2>>t3;
        if(t1==0) {
            modify(1,1,n,t2,t3);
        }
        else {
            Matrix r=query(1,1,n,t2,t3)*zero;
            cout<<r.a[2][1]<<endl;
        }
    }
}

当然似乎这个问题用线段树暴力又短又快

#include <bits/stdc++.h>
using namespace std;

int src[1000005],a[1000005],al[1000005],ar[1000005],s[1000005],n,m,t1,t2,t3,t4;

struct Result {
    int a,al,ar,s;
};

void build(int p,int l,int r) {
    if(l==r) a[p]=al[p]=ar[p]=s[p]=src[l];
    else {
        build(p<<1,l,(l+r)/2),
        build(p<<1|1,(l+r)/2+1,r);
        a[p]=max(max(a[p<<1],a[p<<1|1]),max(max(ar[p<<1],al[p<<1|1]),ar[p<<1]+al[p<<1|1]));
        al[p]=max(al[p<<1],s[p<<1]+max(0,al[p<<1|1]));
        ar[p]=max(ar[p<<1|1],max(0,ar[p<<1])+s[p<<1|1]);
        s[p]=s[p*2]+s[p*2+1];
    }
}

void modify(int p,int l,int r,int pos,int key) {
    if(l==r) a[p]=al[p]=ar[p]=s[p]=key;
    else {
        if(pos<=(l+r)/2) modify(p<<1,l,(l+r)/2,pos,key);
        else modify(p<<1|1,(l+r)/2+1,r,pos,key);
        a[p]=max(max(a[p<<1],a[p<<1|1]),max(max(ar[p<<1],al[p<<1|1]),ar[p<<1]+al[p<<1|1]));
        al[p]=max(al[p<<1],s[p<<1]+max(0,al[p<<1|1]));
        ar[p]=max(ar[p<<1|1],max(0,ar[p<<1])+s[p<<1|1]);
        s[p]=s[p*2]+s[p*2+1];
    }
}

Result query(int p,int l,int r,int ql,int qr) {
    Result res;
    res.a=-1e+8;
    res.al=-1e+8;
    res.ar=-1e+8;
    res.s=-1e+8;
    if(l>qr||r<ql) return res;
    if(l>=ql&&r<=qr) {
        res.a=a[p];
        res.al=al[p];
        res.ar=ar[p];
        res.s=s[p];
        return res;
    }
    else {
        Result cl,cr;
        cl=query(p<<1,l,(l+r)/2,ql,qr);
        cr=query(p<<1|1,(l+r)/2+1,r,ql,qr);
        res.a=max(max(cl.a,cr.a),max(max(cl.ar,cr.al),cl.ar+cr.al));
        res.al=max(cl.al,cl.s+max(0,cr.al));
        res.ar=max(cr.ar,max(0,cl.ar)+cr.s);
        res.s=cl.s+cr.s;
        return res;
    }
}

int main(){
    ios::sync_with_stdio(false);
    cin>>n;
    for(int i=1;i<=n;i++) cin>>src[i];
    build(1,1,n);
    cin>>m;
    for(int i=1;i<=m;i++) {
        cin>>t3>>t1>>t2;
        if(t3) {
            Result res=query(1,1,n,t1,t2);
            cout<<res.a<<endl;
        }
        else {
            modify(1,1,n,t1,t2);
        }
    }
}

原文地址:https://www.cnblogs.com/mollnn/p/11656378.html

时间: 2024-08-29 05:47:09

SP1716 GSS3 - Can you answer these queries III - 动态dp,线段树的相关文章

线段树 SP1716 GSS3 - Can you answer these queries III

SP1716 GSS3 - Can you answer these queries III 题意翻译 n 个数,q 次操作 操作0 x y把A_xAx 修改为yy 操作1 l r询问区间[l, r] 的最大子段和 依旧是维护最大子段和,还是再敲一遍比较好. code: #include<iostream> #include<cstdio> #define ls(o) o<<1 #define rs(o) o<<1|1 using namespace std

SP1716 GSS3 - Can you answer these queries III 线段树

题目传送门:SP1043 GSS1 - Can you answer these queries I 更好的阅读体验 动态维护子段和最大值 前置知识 静态维护子段和最大值:SP1043 GSS1 - Can you answer these queries I 题解传送 题解: 提供结构体指针线段树写法: 设\(l\)为区间左端点, \(r\)为区间右端点: \(ls\)为以\(l\)为左端点的最大子段和, \(rs\)为以\(r\)为右端点的最大子段和; \(sum\)为区间和, \(val\

SP1716 GSS3 - Can you answer these queries III(单点修改,区间最大子段和)

题意翻译 nnn 个数, qqq 次操作 操作0 x y把 AxA_xAx? 修改为 yyy 操作1 l r询问区间 [l,r][l, r][l,r] 的最大子段和 题目描述 You are given a sequence A of N (N <= 50000) integers between -10000 and 10000. On this sequence you have to apply M (M <= 50000) operations: modify the i-th ele

SP1716 GSS3 - Can you answer these queries III

题意:给定n个数a[1]~a[n],有q次操作. 操作 0 x y:把第a[x]修改为y: 操作 1 x y:询问x到y的的最大子段和. 输入:第一行:一个正整数n,表示有n个整数: 第二行:n个整数,表示数列: 第三行:一个正整数q,表示有q个询问: 第4~q+3行:每行三个数p,x,y,表示三种操作. 输出:对于每一个种类为1的询问,输出最大子段和. 输入样例: 41 2 3 441 1 30 3 -31 2 41 3 3 输出样例: 64-3 解析:用线段树进行维护,记录下每一段的和(su

模板——线段树维护最大子段和 SP1716 GSS3 - Can you answer these queries III

${\color{Pink}{>>Question}}$ 1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 #include <cstring> 5 #include <cmath> 6 #define ll long long 7 using namespace std; 8 9 template <typename T> void in

数据结构(线段树):SPOJ GSS3 - Can you answer these queries III

GSS3 - Can you answer these queries III You are given a sequence A of N (N <= 50000) integers between -10000 and 10000. On this sequence you have to apply M (M <= 50000) operations: modify the i-th element in the sequence or for given x y print max{

SPOJ GSS3 Can you answer these queries III (线段树)

题目大意: 求区间最大子区间的和. 思路分析: 记录左最大,右最大,区间最大. 注意Q_L  和 Q_R  就好. #include <cstdio> #include <iostream> #include <algorithm> #include <cstring> #define lson num<<1,s,mid #define rson num<<1|1,mid+1,e #define maxn 55555 using na

H - Can you answer these queries? HDU 4027 (线段树+延迟标记+开根号的速度)

H - Can you answer these queries? Time Limit:2000MS Memory Limit:65768KB 64bit IO Format:%I64d & %I64u Submit Status Practice HDU 4027 Description A lot of battleships of evil are arranged in a line before the battle. Our commander decides to use our

题解 SP1716 【GSS3 - Can you answer these queries III】

\[ Preface \] 没有 Preface. \[ Description \] 维护一个长度为 \(n\) 的数列 \(A\) ,需要支持以下操作: 0 x y 将 \(A_x\) 改为 \(y\) . 1 x y 求 \(\max\limits_{x \leq l \leq r \leq y}{\sum_{i=l}^rA[i]}\) . \[ Solution \] 区间最大子段和 是一个非常经典的问题. 对于 整体最大子段和 来说,一般有 \(O(n)\) 的 贪心 和 分治 做法,