jzoj5976. 【清华2019冬令营模拟12.15】打怪兽(决策单调dp)

32 篇文章 0 订阅
7 篇文章 0 订阅

题目描述

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] f[i][j]=f[i1][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]) f[i][j]=min(f[k2][j(ik+1)]+a[k1]+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]) f[i][j]=min(f[k2][j(ik+1)]+a[k1]+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的状态
再加上滚动数组就可以了

貌似这就是1D1D?

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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值