1. 程式人生 > >題解 [NOI2010]超級鋼琴

題解 [NOI2010]超級鋼琴

!= const www 求解 can 進行 pro nod 每次

題解 [NOI2010]超級鋼琴


終於更博了,之前有點擺


題目

題意:

有一個長為 \(n\) 的數列 \(A\) ,定義區間 \([i, j]\) 的權值為 \(\sum_{k=i}^j A_k\)

現在選出 \(k?\) 個不相同的區間,使得它們的權值和最大. 並滿足每個選出的區間長度∈\([L, R]?\)

暴力 \(n^2?\) 不說了,講正解.

正解:

  1. 快速求出"區間集" \((p,l,r)\) 的最大答案(下文中會對此有解釋)

首先區間 \([l,r]?\) 的答案為 \(sum[r]-sum[l-1]?\) .

那麽如何確保區間長度 \(r-l+1\in [L,R]\)

呢?

顯然,對於一個確定的左端點 \(i?\) ,它的右端點的取值範圍是 \([i+L-1,i+R-1]?\) .

在這 \(R-L+1\) 個左端點為 \(i\) 的區間中,如何選出區間和最大的呢?

對於每一個區間,其區間和為 \(sum[j]-sum[i]\space(j\in[i+L-1,i+R-1])?\)

其中 \(sum[i]\) 為定值,那麽問題轉換為求 \(sum[i]\space(j\in[i+L-1,i+R-1])\) 的最值

既然是\(RMQ\) (區間最值) 那麽就可以用ST表來維護,每次可以 \(\Theta(1)\) 求解.

那麽現在,我們可以求出區間集 {\([l,r]\)

} \(\space(r\in[l+L-1,l+R-1])\) 中的最大區間.

用一個三元組 \((p,l,r)\) 來表示一個左端點為\(p\),右端點屬於\([l,r]\) 區間集 \(S\).

  1. 如何做到答案覆蓋整個區間不遺漏

先以每個位置為左端點構造\(n\) 個三元組,註意邊界.

同時對每個三元組記錄出它的答案,以及取到最大值區間時的右端點位置(這個位置可以預處理倍增數組).

把它們丟到堆裏面,每次取出最大的彈掉,進行\(k\)次.

其中要註意的是在彈掉一個三元組 \((p,l,r)?\) 後,就意味著去掉了所有的區間 \([p,i]\space(i\in[l,r])?\)

然而實際上我們只取出了一個區間 \((p,mid)\)

(\(mid?\) 即為"取到最大值區間時的右端點位置").

所以還要保留區間 \([p,i]\space(i\in[l,mid-1])\) 以及 \([p,i]\space(i\in[mid+1,r])\) 的答案.

所以將這兩個東西加入堆中,並且要算出它們的 \(mid\) .

\(k\) 次就可以了.


代碼:

#include<bits/stdc++.h>
#define il inline 
#define RG register int
#define ll long long
#define gc getchar
using namespace std;
il int rd()
{
    RG x=0,flag=1;
    char ch=0;
    while((ch>'9'||ch<'0')&&ch!='-')ch=gc();
    if(ch=='-')flag=-1,ch=gc();
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=gc();
    return x*flag;
}
const int N=500005,LOG=22;
ll f[N][LOG],sum[N];
int pos[N][LOG],Log[N];
ll Ans;
int n,k,L,R;
ll query(int l0,int r0)
{
    int t=Log[r0-l0+1];
    return max(f[l0][t],f[r0-(1<<t)+1][t]);
}
int query2(int l0,int r0)
{
    int t=log2(r0-l0+1);
    return f[l0][t]>f[r0-(1<<t)+1][t]?pos[l0][t]:pos[r0-(1<<t)+1][t];
}
struct node {
    int p,l,r,mid; ll res;
    bool operator <(node x)const {return res<x.res;}
};
priority_queue<node> q; 
int main ()
{
    scanf("%d%d%d%d",&n,&k,&L,&R);
    int LogN=log2(n)+1;
    for(RG i=1;i<=n;i++)sum[i]=sum[i-1]+rd();
    Log[0]=-1;
    memset(f,0xcf,sizeof(f));
    for(RG i=1;i<=n;i++) f[i][0]=sum[i],pos[i][0]=i,Log[i]=Log[i>>1]+1;
    for(RG j=1;j<=LogN;j++) 
        for(RG i=1;i+(1<<j)<=n+1;i++) 
        {
            if(f[i][j-1]>f[i+(1<<j-1)][j-1])
            {
                f[i][j]=f[i][j-1];
                pos[i][j]=pos[i][j-1];
            }
            else
            {
                f[i][j]=f[i+(1<<j-1)][j-1];
                pos[i][j]=pos[i+(1<<j-1)][j-1];
            }
        }
    for(RG i=1;i+L-1<=n;i++)
    {
        ll t=query(i+L-1,min(i+R-1,n))-sum[i-1];
        int midt=query2(i+L-1,min(n,i+R-1));
        q.push(node{i,i+L-1,min(i+R-1,n),midt,t});//
    }
    for(RG i=1;i<=k;i++)
    {
        Ans+=q.top().res;
        node t=q.top();
        int p0=t.p,l0=t.l,r0=t.r,mid0=t.mid;
        q.pop();
        if(mid0>l0)
            q.push(node{p0,l0,mid0-1,query2(l0,mid0-1),query(l0,mid0-1)-sum[p0-1]});
        if(mid0<r0)
            q.push(node{p0,mid0+1,r0,query2(mid0+1,r0),query(mid0+1,r0)-sum[p0-1]});
    }
    cout<<Ans<<endl;
    return 0;
}

題解 [NOI2010]超級鋼琴