[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-1x−1個學生,是否是特殊資料。
- tp=0,第二行nn個整數a_{1\dots n}a1…n,a_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 }