1. 程式人生 > >【CF1077F2】Pictures with Kittens 單調佇列+dp

【CF1077F2】Pictures with Kittens 單調佇列+dp

題目大意:給定一個長度為 N 的序列,點有點權,從序列中選出恰好 X 個數,並且保證任意連續的 K 個數中均有一個被選中,求選出的點權最大是多少。

題解:此題可以作為 烽火傳遞+ 來處理,只不過在烽火傳遞的基礎上加了選出恰好 X 個數,因此只需在狀態維度上加上一維選出的個數即可,\(dp[i][j]\) 表示前 i 個數中選出 j 個數,且第 i 個數被選中的最優解,因此有狀態轉移方程:\(dp[i][j]=max\{dp[k][j-1],k\in[i-m,i-1] \}+val[i]\),直接用單調佇列進行優化即可。

同時,可以直接在初始化的時候將所有值設為無窮,避免了無效的狀態轉移,從而避免了不合法的解對答案的貢獻,也就避免了討論何時輸出 -1 的問題。

程式碼如下

#include<bits/stdc++.h>
using namespace std;
const int maxn=5010;

int n,m,tot,val[maxn],q[maxn<<1],l,r;
long long dp[maxn][maxn],ans;

void read_and_parse(){
    scanf("%d%d%d",&n,&m,&tot);
    for(int i=1;i<=n;i++)scanf("%d",&val[i]);
}

void solve(){
    memset(dp,0xcf,sizeof(dp));
    dp[0][0]=0;
    for(int i=1;i<=tot;i++){
        l=1,r=0;
        for(int j=1;j<=n;j++){
            while(l<=r&&q[l]<j-m)++l;
            while(l<=r&&dp[j-1][i-1]>dp[q[r]][i-1])--r;
            q[++r]=j-1;
            dp[j][i]=dp[q[l]][i-1]+val[j];
        }
    }
    ans=-1;
    for(int i=n-m+1;i<=n;i++)ans=max(ans,dp[i][tot]);
    printf("%lld\n",ans);
}

int main(){
    read_and_parse();
    solve();
    return 0;
}