1. 程式人生 > 實用技巧 >[ACOI2020] 課後期末考試滑溜滑溜補習班(單調佇列優化dp)

[ACOI2020] 課後期末考試滑溜滑溜補習班(單調佇列優化dp)

題目

Description

潮田 渚(Shiota Nagisa)因為理科不大好,自然會被仔細觀察學生的殺老師發現,於是渚同學只得加入殺老師舉辦的課後期末考試滑溜滑溜補習班。至於為什麼叫這個名字,額,你不能問我啊。

在補習班上,因為多個學生會同時有需求,所以殺老師會製造分身用音速移動來回回答問題。

補習班上有n個同學,他們每一個人都有一個問題。殺老師為了有序回答學生的問題,把所有學生排成了一列。第i個學生的問題有一個困難值ai,殺老師回答第i個學生的問題需要花費ai的精力。殺老師到了哪裡,它就要解決那個學生的問題。殺老師最開始會解決序列中第一個同學的問題,他最後會去解決最後一個同學的問題。

殺老師每次解決完一個同學的問題到下一個同學的座位上就要花費kk點精力值。特殊的,如果殺老師想讓自己輕鬆一點,可以不移動到下一個,可以直接到下兩個,下三個,就不用解決跳過的同學的問題了。對應的,它會被學生調侃。受到打擊的殺老師自然會花費格外的精力,花費的精力為k+(q-p-1) \times d當前位置為p,跳到的位置為q)。

當然的,殺老師也是有速度的啊,並且它想解決學生的一些問題,所以說殺老師最多隻會跳過x-1個學生,去解決下x個學生的問題。

Input

  • 第一行五個整數n,k,d,x,tpn,k,d,x,tp,表示有nn個學生,只按順序去到下一個學生的座位需要花費kk點精力,每多跳過一個學生就要多花費dd點精力值,每一次最多隻能跳過x-1x1個學生,是否是特殊資料。

  • tp=0,第二行nn個整數a_{1\dots n}a1na_iai表示第ii個學生的問題的困難值為a_iai
  • tp=1,第二行一個整數SeedSeed,作為種子,然後呼叫rndrnd函式依次生成nn個整數,作為aa陣列,a_iai表示第ii個學生的問題的困難值為a_iai
inline int rnd () {
    static const int MOD = 1e9;
    return Seed = ( 1LL * Seed * 0x66CCFF % MOD + 20120712 ) % MOD;
}

Output

一行一個整數,表示殺老師解決完最後一個同學的問題最少需要花費多少精力。

Sample Input1

5 3 4 1 0
1 2 3 4 5

Sample Output1

27

Sample Input2

10 30630 56910 2 0
7484 99194 86969 17540 29184 68691 91892 81564 93999 74280

Sample Output2

717318

Sample Input3

10000000 899999999 923456655 213111 1
1314520

Sample Output3

9231813656566921

Hint

樣例1:殺老師每次不能跳過學生,因此他必須依次移動並解決所有問題,故答案為解決問題所需的精力1+2+3+4+5=151+2+3+4+5=15與移動所需的精力4 \times 3=124×3=12,所以花費精力之和為2727。


思路

首先很明顯是一道動態規劃的題;

我們設 $dp[i]$ 表示解決到第 $i$ 個同學的問題,需要花費的最小精力;

那麼 $dp$ 轉移方程就是

$dp[i]=min(dp[i],dp[j]+a[i]+k+(i-j-1)*d) $ ;

方程意思是從解決了第 $j$ 個同學到解決第 $i$ 個同學的問題所消耗的精力;

$a[i]$ 是第$i$ 個問題解決需要的精力 ,$(i-j-1)*d$ 表示跳過同學被吐槽消耗的精力;

具體跳過消耗的精力題目以給出求法,請仔細看題!!!;

那麼我們很驚奇的發現:

方程可以轉化一下,變成:

$dp[i]=min(dp[i],dp[j]-j*d+a[i]+i*d+k-d) $ ; (把 $ (i-j-1) \times d$ 分配開);

那麼方程求最小值就與$j$ 有關了;

我們就可以把 $j$ 放到一個單調佇列裡,每次找一個 $min(dp[j]-j*d)$ ;

那麼什麼時候踢隊頭呢,題目限制跳過的同學不能超過 $x-1$ ;

所以如果 $i-q[head]-1~>~x-1$ 就要踢隊頭;

這樣就 $ok$ 了,$AC$ 了;

程式碼

#include<bits/stdc++.h>
#define re register//上面為啥沒有註釋 
typedef long long ll;
using namespace std;
inline ll read()
{
    ll a=0,f=1; char c=getchar();
    while (c<'0'||c>'9') {if (c=='-') f=-1; c=getchar();}
    while (c>='0'&&c<='9') {a=a*10+c-'0'; c=getchar();}
    return a*f;
}
ll n,k,d,x,tp;
ll Seed;
ll a[10000010];
ll dp[10000010];
ll q[10000010];
inline ll rnd () 
{
    static const ll MOD = 1e9;
    return Seed = ( 1LL * Seed * 0x66CCFF % MOD + 20120712 ) % MOD;
}//題目中給的生成每個 a[i] 
int main()
{
    memset(dp,127/3,sizeof(dp));//初始 
    n=read(); k=read(); d=read(); x=read(); tp=read();//讀入 
    if(tp==0)//讀入 a[i] 
    {
        for(re ll i=1;i<=n;i++)
            a[i]=read();
    }
    else//生成 a[i] 
    {
        Seed=read();
        for(re ll i=1;i<=n;i++)
            a[i]=rnd();
    }
    dp[0]=0; dp[1]=a[1];//初始化,題目要求第一個同學的必須解決 
    ll head=1,tail=0;// 
    for(re ll i=1;i<=n;i++)
    {
        while(head<=tail&&i-q[head]-1>x-1)//跳過的同學超過限制 
            head++;//踢隊頭 
        if(i-q[head]-1>=0)//狀態轉移方程 
            dp[i]=min(dp[i],dp[q[head]]+a[i]+k+(i-q[head]-1)*d);
        while(head<=tail&&dp[q[tail]]-q[tail]*d>dp[i]-i*d)//尋找最小的 dp[j]-j*d)
            tail--;//踢隊尾 
        q[++tail]=i;//入隊 
    }
    printf("%lld\n",dp[n]);//輸出 
    return 0;//AC 
}