1. 程式人生 > >常州大學新生寒假訓練會試 D-訓練技巧【動態規劃】

常州大學新生寒假訓練會試 D-訓練技巧【動態規劃】

題目描述

常州大學組織了新生寒假訓練一共N天,每天訓練可以獲得的訓練效果是Ei。但是如果連續訓練超過K天,萌新們會受不了而被勸退。

現在負責人想知道,如何安排能保證萌新不會被勸退並且能獲得最大的訓練效果。

輸入描述:

第一行:兩個用空格隔開的整數:NK1≤N≤1000001≤K≤N
第二行到N+1行:第i+1行有一個整數,表示第N天的訓練效果是Ei(0 <= Ei <= 1,000,000,000

輸出描述:

第一行:單個整數,表示最大的能力之和

示例1

輸入

5 2

1

2

3

4

5

輸出

12

說明

(除了第三天以外每天都在訓練,總訓練效果為1+2+4+5=12

備註:

1≤n≤100,000

【題意】

RT

【思路】

我們用dp[i]表示第i天不訓練的情況下前i天損失的訓練效果。

我們可以得到狀態轉移方程:

dp[i]=min{dp[j]+a[i]} (i-j<=k)

也就是我們要在【i-k,i】迴圈取一個最小的值,但是顯然這樣的複雜度過大,我們考慮用單調佇列優化。

具體實現見程式碼。

#include <cstdio>
#include <cmath>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
#define mst(a,b) memset((a),(b),sizeof(a))
#define rush() int T;scanf("%d",&T);while(T--)

typedef long long ll;
const int maxn = 100005;
const ll mod = 1e9+7;
const ll INF = 1e18;
const double eps = 1e-9;

int n,k;
int a[maxn];
int q[maxn];
ll dp[maxn];

int main()
{
    while(~scanf("%d%d",&n,&k))
    {
        mst(dp,0);
        int st=0,ed=0;
        ll sum=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%lld",&a[i]);
            sum+=a[i];
        }
        for(int i=1;i<=n;i++)
        {
            dp[i]=dp[q[st]]+a[i];          
            while(st<=ed&&dp[q[ed]]>dp[i]) ed--;  //保證單調佇列中的資料從小到大排列
            q[++ed]=i;
            if(q[st]<i-k) st++;                   //確保單調佇列中的資料都滿足限制條件
        }
        ll ans=INF;
        for(int i=n-k;i<=n;i++)                   //求出損失的最小訓練效果
        {
            ans=min(ans,dp[i]);
        }
        printf("%lld\n",sum-ans);
    }
    return 0;
}