jzoj5976. 【清華2019冬令營模擬12.15】打怪獸(決策單調dp)
題目描述
Description
Input
Output
Sample Input
4
3 1 0 2
Sample Output
5
4
3
1
Data Constraint
20%
暴力不解釋
50%
首先如果只在一個位置加護甲,則造成的影響顯然是一個階梯狀的塊
那麼有一個很顯然的性質:
兩個塊之間不會相鄰
因為可以把後面的移到前面,這樣肯定不會更劣
所以O(n^3)的dp很顯然,設f[i][j]表示最後一塊的末尾為1~i之一,一共加的護甲值為j時的最小答案
設s[i][j]表示從j到i(j≤i)段的貢獻,且在位置j加了(i-j+1)的護甲值(也就是說到i時護甲值為1)
顯然一種轉移是
(位置i不放)
那麼如果i位置放的話,則
(k為當前塊的末尾)
考慮到最後一塊的護甲值可能沒有用完(就是末尾在n之外),可以把n乘以2,多的部分當做0
100%
觀察一下dp式子:
然後可以發現當k-1時,兩個量都-1
所以實際上一個狀態的轉移是一條斜線,就是起點為(i-2,j-1)方向左上的一條線(當k=1時)
所以可以按照(i-j)分類,每一類之間單獨考慮
還有一個性質,當i+1時,s[i][j]的增量單調不減
考慮j和k(j<k)兩個位置
當i+1時,j和k都加了a[i+1],但j的位置較前,所以有用的護甲值肯定不小於k
則j減的數大於等於k,所以s的增量單調不減
決策單調
然後對於兩個決策j和k(j<k),如果在加入時j的值小於k,則k肯定沒有用,因為k的增量比j大
那麼可以對於每類情況維護出一個單調棧,保證加入時的初值(就是f+s)單調
(因為i的情況已經搞完了,所以考慮的是i+1時的s)
但是這樣搞有點van♂題,因為可能隨著i的增大就不滿足單調了
事實上,決策單調指的是兩個決策之間關係的單調,但最終的決策可能不是單調(這和一般的決策單調不同但也可能是我太弱了)
比如說有三個狀態,則最終的決策可能是這樣
11112222111133331111
顯然1這個狀態出現了至少三段
因為本題中的決策單調只存在於兩個狀態之間,所以每次只考慮相鄰兩個狀態
對於每個狀態,求出該狀態與上一個狀態是失效時間(就是超過這個時間後這個狀態就不如上個狀態),然後按照失效時間來維護單調棧(從大到小)
對於新加入的一個狀態,如果當前狀態的初值比上個狀態優,且失效時間比上個狀態大,則上個狀態顯然沒用了
(因為在其失效時間以內,當前狀態都比上個狀態優,且超過失效時間後則不如上上個狀態)
然後每次用棧頂來更新
至於正確性的證明,可以考慮是否保留了每個時刻的最優狀態
首先按照初值維護單調棧,沒有加入的一定不會再某個時刻由於棧頂,所以可以不加
然後每次踢掉的狀態在失效之前不如下一個,失效之後不如上一個,也不會成為最優
所以最優狀態一定得以保留
然後根據單調棧性質,在i+1時刻時下一個肯定比上一個優,則每次用棧頂更新就OK了
還有因為n最大為4000,所以直接*2空間會炸
但顯然可以發現i-j+n不會超過2n(超過2n就沒用了,因為塊的開頭不在n以內),所以只用保留i-j+n<=2n的狀態
再加上滾動陣列就可以了
code
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#define fo(a,b,c) for (a=b; a<=c; a++)
#define fd(a,b,c) for (a=b; a>=c; a--)
#define max(a,b) (a>b?a:b)
#define min(a,b) (a<b?a:b)
using namespace std;
int a[8002];
int f[4002];
int F[4002];
int d[8002][4002];
int s[8002][4002];
int d2[8002][4002];
int d3[8002][4002];
int len[8002];
int N,n,i,j,k,l,I,S;
int get(int I,int x,int y)
{
int l=d2[I][y],r=N,mid;
while (l<r)
{
mid=(l+r)/2;
if ((d[I][x]+s[mid][d2[I][x]])>(d[I][y]+s[mid][d2[I][y]]))
l=mid+1;
else
r=mid;
}
l+=((d[I][x]+s[l][d2[I][x]])>(d[I][y]+s[l][d2[I][y]]));
return l;
}
int main()
{
freopen("griffin.in","r",stdin);
freopen("griffin.out","w",stdout);
scanf("%d",&n);N=n+n;
fo(i,1,n)
scanf("%d",&a[i]);
fo(i,1,N)
{
fd(k,i,1)
s[i][k]=s[i][k+1]+max(a[k]-(i-k+1),0);
}
memset(F,1,sizeof(F));
F[0]=0;
fo(i,1,N)
{
fo(j,0,n)
{
f[j]=F[j];
F[j]=F[j]+a[i];
}
fo(j,0,n)
{
I=i-j+n;
if (I>N) continue;
if (i<=j)
F[j]=min(F[j],s[i][1]);
if (len[I])
F[j]=min(F[j],d[I][len[I]]+s[i][d2[I][len[I]]]);
if (i>=2)
{
if (!len[I])
{
++len[I];
d[I][len[I]]=f[j]+a[i];
d2[I][len[I]]=i+1;//d2表示當前狀態的下一塊開頭
d3[I][len[I]]=233333333;
}
else
{
while (i+1>=d3[I][len[I]]) --len[I];
if (d[I][len[I]]+s[i+1][d2[I][len[I]]]>f[j]+a[i]+s[i+1][i+1])
{
d[I][0]=f[j]+a[i];
d2[I][0]=i+1;
while (get(I,len[I],0)>=d3[I][len[I]])
--len[I];
S=get(I,len[I],0);
if (i+1<S)
{
++len[I];
d[I][len[I]]=f[j]+a[i];
d2[I][len[I]]=i+1;
d3[I][len[I]]=S;
}
}
}
}
}
}
fo(i,1,n)
printf("%d\n",F[i]);
fclose(stdin);
fclose(stdout);
return 0;
}