POJ1821 Fence
这道题是一道很好的单调队列优化DP的例子。
题目大意是有n个工人,每个人可以粉刷一段长度不超过l[i]的墙,如果一个人粉刷了那么他必须要粉刷第s[i]块墙,一个人粉刷一块墙能得到p[i]的钱,求所有工人得到的钱的最大值。
我们首先把所有工人按s[i]排序,这样方便我们线性DP。
考虑DP,用dp[i][j]表示前i个工人刷j块的得到的钱的最大值,那么我们分类讨论,首先是一个人不刷和一块墙不刷的情况,那么就有dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
之后,我们考虑这个工人刷墙的情况。因为这个人必须要刷s[i],那这个人的转移必然是从s[i] - l[i] ~ s[i]这一段转移过来的。否则的话就成了不合法情况。那么就有dp[i][j] = max{dp[i-1][k] + (j - k) * p[i]} (s[i] - l[i] <= k <= s[i])
这个式子我们朴素的做法是先枚举i,对于每一个工人i,枚举它粉刷的块数j,之后再枚举转移的范围。不过这样会超时,我们考虑优化。我们发现如果把j,k分离出来,那么对于每一个j(状态),它对应的j * p[i]是一个定值,只有内部的式子随着k(决策)的变化而改变,而且其实对于每一个j,有很大一部分的可选取的决策区间是重复的。
说到这里,我们就想到用单调队列维护啦!!那么我们的想法就是,对于每一个工人,我们首先把s[i] - l[i] ~ s[i]这一段区间之内的所有决策压入单调队列,之后随着j的增加,所能选取的k的区间左端点随之变大。也就是说实际上,我们只要维护一段左端点变大,右端点不动的区间最大值就可以了。
所以一般来说,单调队列优化DP的套路就是,如果一个DP方程能被拆成每一个状态都是从一个决策区间中选取最优的转移,然后决策区间随着状态变化左右端点变化,而且有大量重复,我们就可以先拆分式子,拆成内层只与决策有关,这样直接用单调队列维护决策的极值,就省去了一层循环,优化了时间。
看一下代码。
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<cmath> #include<set> #include<queue> #define rep(i,a,n) for(int i = a;i <= n;i++) #define per(i,n,a) for(int i = n;i >= a;i--) #define enter putchar('\n') using namespace std; typedef long long ll; const int M = 10005; const int INF = 1000000009; int read() { int ans = 0,op = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') op = -1; ch = getchar(); } while(ch >= '0' && ch <= '9') { ans *= 10; ans += ch - '0'; ch = getchar(); } return ans * op; } struct worker { int l,p,s; bool operator < (const worker &g) const { return s < g.s; } }a[105]; int n,k,dp[105][20005],q[20005],head,tail; int calc(int i,int x) { return dp[i-1][x] - x * a[i].p; } int main() { n = read(),k = read(); rep(i,1,k) a[i].l = read(),a[i].p = read(),a[i].s = read(); sort(a+1,a+1+k); rep(i,1,k) { head = 1,tail = 0; rep(j,max(0,a[i].s-a[i].l),a[i].s-1) { while(head <= tail && calc(i,q[tail]) < calc(i,j)) tail--; q[++tail] = j; } rep(j,1,n) { dp[i][j] = max(dp[i-1][j],dp[i][j-1]); if(j >= a[i].s) { while(head <= tail && q[head] < j - a[i].l) head++; if(head <= tail) dp[i][j] = max(dp[i][j],calc(i,q[head]) + j * a[i].p); } } } printf("%d\n",dp[k][n]); return 0; }