CF280D k-Maximum Subsequence Sum

\[\color{#66ccff}{\texttt{->原題傳送門<-}}\]

【題目描述】

長度爲\(n\)\(n \le 10^5\))的數列\(a_i\),進行\(m\)次操做(\(m \le 10^5\))支持兩種操做:node

  • \(0 \; i \; val :\)\(a_i\)修改成\(val\)ios

  • \(1 \; l \; r \; k:\) 詢問區間\([l,r]\)裏選出至多\(k\)個不相交的子段和的最大值。(\(\sum_{}k \le 10^5\)ui

【題解】

先考慮幾個簡單的問題:this

  1. \(k=1\),則該問題就是\(GSS3\)spa

  2. 若沒有修改操做,而且操做\(2\)\(l=一、r=n\),則是一個經典的最大\(k\)段子段和問題。code

問題\(1\)不用多說,用線段樹維護\(Lmax、Rmax、Sum、Ans\)便可。get

對於問題\(2\),考慮貪心,取\(k\)次,每次都選擇最大子段和。it

然而,一個數最多隻能被選一次,因此每選一次後要刪除它對後續操做的影響。io

容易發現一個結論:咱們每次把所選區間內的數進行取反操做便可刪除影響(即選不到這個數)。class

這一點能夠根據費用流的思想取證實,這裏就再也不贅述。

如今咱們考慮如何高效的模擬這個過程。

首先,這個過程能與問題\(1\)的線段樹解法相結合呢?

答案是確定的。

咱們發現,無論多少次取負,一個位置的數只有兩種可能(一正一負),因此咱們能夠維護兩顆線段樹。

合併時分別合併,取負時找到對應區間,交換二者信息 並打上標記便可。

再結合問題\(1\)的單點修改,這個問題就能夠獲得完美解決。

\(Code:\)(內有部分良心註釋)

#include<cstdio>
#include<algorithm>
#include<queue>
#include<stack>
using namespace std;
#define ll long long
#define rg register
struct ios{
    template<typename TP>
    inline ios operator >> (TP &x)
    {
        TP f=1;x=0;rg char c=getchar();
        for(;c>'9' || c<'0';c=getchar()) if(c=='-') f=-1;
        for(;c>='0' && c<='9';c=getchar()) x=(x<<3)+(x<<1)+(c^'0');
        x*=f;
        return *this;
    }
    template<typename TP>
    inline ios operator << (TP x)
    {
        int top=0,s[66];
        if(x<0) x=-x,putchar('-');
        if(!x) putchar('0');
        while(x) s[++top]=x%10+'0',x/=10;
        while(top) putchar(s[top--]);
        return *this;
    }
    inline ios operator << (char s)
    {
        putchar(s);
        return *this;
    }
}io;
#define ls rt<<1
#define rs rt<<1|1
#define lson l,mid,ls
#define rson mid+1,r,rs
const int N=1e5+5;
int n,m,a[N];
bool Tag[N<<2];
struct node{
    int Lmax,Rmax,Ans,Sum;
        /*
                Lmax:區間從左端開始的最大子段和
                Rmax:區間從右端開始的最大子段和
                Sum:區間和
                Ans:區間最大子段和
        */
    int L,R,Ansl,Ansr;
    /*
        L:區間左端最長子段的右端點
        R:區間右端最長子段的左端點
        ↓↓↓↓↓↓↓↓↓↓維護↓↓↓↓↓↓↓↓↓↓
        Ansl:當前區間的最長子段的左端點
        Ansr:當前區間的最長子段的右端點

        建一正一負兩顆線段樹
        但一段區間在兩種狀況下的最長子段和的值和對應的位置不必定相同
        因此要維護Ansl、Ansr
        維護Ansl、Ansr就要用到L、R
    */
    inline node(rg int pos=0,rg int val=0) {L=R=Ansl=Ansr=pos,Lmax=Rmax=Ans=Sum=val;}
}t[2][N<<2];
stack<node>q;
inline node operator + (node A,node B)
{
    node Ans;
    if(A.Lmax>A.Sum+B.Lmax) Ans.Lmax=A.Lmax,Ans.L=A.L;
    else Ans.Lmax=A.Sum+B.Lmax,Ans.L=B.L;
        //容易發現一個區間的Lmax只多是左兒子的Lmax或右兒子的Lmax+左兒子的區間和

    if(B.Rmax>A.Rmax+B.Sum) Ans.Rmax=B.Rmax,Ans.R=B.R;
    else Ans.Rmax=B.Sum+A.Rmax,Ans.R=A.R;
        //同上

    Ans.Sum=A.Sum+B.Sum;
    if(A.Ans>B.Ans) Ans.Ans=A.Ans,Ans.Ansl=A.Ansl,Ans.Ansr=A.Ansr;
    else Ans.Ans=B.Ans,Ans.Ansl=B.Ansl,Ans.Ansr=B.Ansr;
    if(A.Rmax+B.Lmax>Ans.Ans) Ans.Ans=A.Rmax+B.Lmax,Ans.Ansl=A.R,Ans.Ansr=B.L;
    return Ans;
}
inline void pushup(int rt)
{
    t[0][rt]=t[0][ls]+t[0][rs];
    t[1][rt]=t[1][ls]+t[1][rs];
}
inline void build(int l,int r,int rt)
{
    // io<<l<<' '<<r<<' '<<rt<<'\n';
    if(l==r)
    {
        t[0][rt]=node(l,a[l]);
        t[1][rt]=node(l,-a[l]);
        return;
    }
    int mid=(l+r)>>1;
    build(lson),build(rson);
    pushup(rt);
}
inline void Get(int rt)
{
    swap(t[0][rt],t[1][rt]);
    Tag[rt]^=1;
}
inline void update(int p,int val,int l,int r,int rt)
{
    // io<<l<<' '<<r<<' '<<rt<<'\n';
    if(l==r)
    {
        t[0][rt]=node(l,val);
        t[1][rt]=node(l,-val);
        return;
    }
    if(Tag[rt])
    {
        Get(ls),Get(rs);
        Tag[rt]=0;
    }
    int mid=(l+r)>>1;
    if(p<=mid) update(p,val,lson);
    else update(p,val,rson);
    pushup(rt);
}
inline void Modify(int L,int R,int l,int r,int rt)//區間取反
{
    if(L<=l && r<=R)
    {
        Get(rt);
        return;
    }
    if(Tag[rt])
    {
        Get(ls),Get(rs);
        Tag[rt]=0;
    }
    int mid=(l+r)>>1;
    if(L<=mid) Modify(L,R,lson);
    if(R>mid) Modify(L,R,rson);
    pushup(rt);
}
inline node query(int L,int R,int l,int r,int rt)//查詢能夠覆蓋區間[L,R]的節點的信息
{
    if(L<=l && r<=R) return t[0][rt];
    if(Tag[rt])
    {
        Get(ls),Get(rs);
        Tag[rt]=0;
    }
    int mid=(l+r)>>1;
    if(R<=mid) return query(L,R,lson);
    else if(L>mid) return query(L,R,rson);
    else return query(L,mid,lson)+query(mid+1,R,rson);
}
int main()
{
    // freopen("CF280D.in","r",stdin);
    io>>n;
    for(rg int i=1;i<=n;++i) io>>a[i];
    build(1,n,1),io>>m;
    for(rg int _=1,op,l,r,k;_<=m;++_)
    {
        io>>op;
        if(!op)
        {
            io>>l>>r;
            update(l,r,1,n,1);
        }
        else
        {
            io>>l>>r>>k;int ans=0;
            for(rg int i=1;i<=k;++i)
            {
                node Ans=query(l,r,1,n,1);
                if(Ans.Ans<=0) break;
                // io<<Ans.Ans<<' ';
                ans+=Ans.Ans,q.push(Ans);//記錄被影響的區間,查詢完成後再撤銷
                Modify(Ans.Ansl,Ans.Ansr,1,n,1);
            }
            // io<<'\n';
            io<<ans<<'\n';
            while(!q.empty())
            {
                node t=q.top();q.pop();
                Modify(t.Ansl,t.Ansr,1,n,1);
            }
        }
    }
    return 0;
}
/*
15
-4 8 -3 -10 10 4 -7 -7 0 -6 3 8 -10 7 2
15
1 3 9 2
0 5 -10
1 3 9 2
*/
/*
15
-4 8 -3 -10 10 4 -7 -7 0 -6 3 8 -10 7 2
15
1 3 9 2
1 6 12 1
0 6 5
0 10 -7
1 4 9 1
1 7 9 1
0 10 -3
1 4 10 2
1 3 13 2
1 4 11 2
0 15 -9
0 13 -9
0 11 -10
1 5 14 2
1 6 12 1
*/
/*
20
-5 -1 -9 -6 4 -5 6 1 5 -3 6 -3 10 1 4 -10 -10 -9 10 -6
20
0 19 0
1 1 14 3
1 8 20 4
1 9 11 1
1 17 19 1
0 13 4
0 9 7
1 2 11 2
0 20 -8
1 8 19 1
0 2 3
1 6 11 1
0 6 -10
1 8 13 1
1 9 15 1
0 17 -8
1 3 13 1
1 1 14 4
0 17 0
1 7 19 1
*/
相關文章
相關標籤/搜索