1. 程式人生 > >jzoj5976. 【清華2019冬令營模擬12.15】打怪獸(決策單調dp)

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)
顯然一種轉移是 f

[ i ] [ j ] = f [ i
1 ] [ j ] + a [ i ]
f[i][j]=f[i-1][j]+a[i]
(位置i不放)
那麼如果i位置放的話,則
f [ i ] [ j ] = m i n ( f [ k 2 ] [ j ( i k + 1 ) ] + a [ k 1 ] + s [ i ] [ k ] ) f[i][j]=min(f[k-2][j-(i-k+1)]+a[k-1]+s[i][k]) (k為當前塊的末尾)

考慮到最後一塊的護甲值可能沒有用完(就是末尾在n之外),可以把n乘以2,多的部分當做0

100%

觀察一下dp式子:
f [ i ] [ j ] = m i n ( f [ k 2 ] [ j ( i k + 1 ) ] + a [ k 1 ] + s [ i ] [ k ] ) f[i][j]=min(f[k-2][j-(i-k+1)]+a[k-1]+s[i][k])
然後可以發現當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;
}