区间dp+决策单调性
part1.朴素dp
记(dp[l][r])表示l到r这个区间最坏情况下的最小花费。
那么考虑在([l,r])中任选一个点k挖,此时有两种情况:
1.k点有油,那么后续代价就是(dp[k+1][r])
2.k点没有油,那么后续代价就是(dp[l][k-1])
显然,为了使情况最坏,我们要在(dp[k+1][r])和(dp[l][k-1])中取最大值。
而要使最坏情况下花费最小,我们要在所有k中取最小值。
那么,转移方程就是:
[dp[l][r]=min{max{dp[l][k-1],dp[k+1][r] }+A[k]} kin[l+1,r-1]
]
其中(A[i])表示在点i勘测是否有油的花费。
这样,就有一个(O(n^3))朴素dp了,期望得分30分。
part2.决策单调性优化
我们要的是([l,r])的最小值,所以应该是维护单调递增的队列。
但还有一个问题,转移方程中有(max{dp[l][k-1],dp[k+1][r] }),我们要想办法把这个max消掉。
那如何消去max呢?
我们维护一个k,使得(dp[l][k-1])刚好大于(dp[k+1][r])或者(dp[k+1][r])刚好大于(dp[l][k-1])。
现在,我们只需证明k是具有单调性的就可以了。
首先,不难发现这样一个性质:对于任意的l,r,都有(dp[l][r]le dp[l][r+1])。
我们以维护(dp[l][k-1])刚好大于(dp[k+1][r])为例。
那么,假设l,k固定,随着r的不断增大,(dp[k+1][r])也会不断增大,而(dp[l][k-1])不变,因此k肯定会不断向右移。
同理,维护(dp[k+1][r])刚好大于(dp[l][k-1])时,k肯定也会不断向左移。
于是,我们在维护最小值的同时再不断弹出队头不合法的元素,这样就保证转移一定是从(dp[l][k-1])或者(dp[k+1][r])转移过来的。
代码:
#include<bits/stdc++.h>
using namespace std;
int n,A[2010],dp[2010][2010],Q[2010][2010],head[2010],tail[2010];
//要以每个点都作为l和r的情况各开一个单调队列
//但是由于dp中l是第一层循环,那么枚举一个l后就不会在遍历到了,因此可以把所有点作为l的情况公用一个单调队列
//那么开n+1个单调队列就可以了
int calcl(int l,int r,int k){
return dp[l][k-1]+A[k];
}
int calcr(int l,int r,int k){
return dp[k+1][r]+A[k];
}
int main(){
memset(dp,63,sizeof(dp));
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&A[i]);
for(int i=1;i<=n;i++)head[i]=1;
for(int l=n;l>=1;l--){
head[0]=1;
tail[0]=0;
dp[l][l]=A[l];
Q[0][++tail[0]]=l;
for(int r=l+1;r<=n;r++){
while(head[0]<=tail[0]&&calcl(l,r,Q[0][head[0]])<calcr(l,r,Q[0][head[0]]))head[0]++;
while(head[0]<=tail[0]&&calcl(l,r,r)<calcl(l,r,Q[0][tail[0]]))tail[0]--;
Q[0][++tail[0]]=r;
while(head[r]<=tail[r]&&calcr(l,r,Q[r][head[r]])<calcl(l,r,Q[r][head[r]]))head[r]++;
while(head[r]<=tail[r]&&calcr(l,r,l)<calcr(l,r,Q[r][tail[r]]))tail[r]--;
Q[r][++tail[r]]=l;
dp[l][r]=min(calcl(l,r,Q[0][head[0]]),calcr(l,r,Q[r][head[r]]));
}
}
cout<<dp[1][n];
return 0;
}