题目描述
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[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])
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])
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的状态
再加上滚动数组就可以了
貌似这就是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;
}