【BZOJ】P2448 挖油
区间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中取最小值。
那么,转移方程就是:
其中\(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;
}