http://poj.org/problem?id=1821
题目
有n块木板排成一行,有k个工人,第$i$个工人坐在第$s_i$块木板处,每刷一块木板的工钱是$p_i$,他只能选择粉刷长度不超过$l_i$,包含$s_i$的连续的一段木板,也可以不粉刷。
每块木板要么只由一个工人粉刷,要么不粉刷
问这些工人能得到的工钱的和最大是多少
$n\leqslant16000\quad k\leqslant100$
题解
设dp[i][j]为前i个工人刷了前j块木板
那么
$dp[i][j]=dp[i][j-1]$
$dp[i][j]=dp[i-1][j]$
$dp[i][j]=\max\{dp[i-1][k]+(j-k)\times p_i|k<s_i,j\geqslant s_i,j-k\leqslant l_i\}$
直接转移,时间复杂度$\mathcal{O}(n^2k)$无法接受
改写一下
$dp[i][j]=j\times p_i+\max\{dp[i-1][k]-k\times p_i|j-l_i\leqslant k< si\}$
发现max的范围是不断向后移动的,因此可以使用单调队列(滑动窗口)
那么时间复杂度$\mathcal{O}(nk)$
AC代码
#include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<vector> #include<map> #define REP(i,a,b) for(register int i=(a); i<(b); i++) #define REPE(i,a,b) for(register int i=(a); i<=(b); i++) #define PERE(i,a,b) for(register int i=(a); i>=(b); i--) using namespace std; typedef long long ll; int n,m; #define MAXN 16007 #define MAXM 107 struct node { int l,p,s; bool operator<(const node&r) const { return s<r.s; } } a[MAXM]; int dp[MAXM][MAXN]; int dw[MAXN], di[MAXN]; int main() { scanf("%d%d", &n, &m); REP(i,1,m) scanf("%d%d%d", &a[i].l, &a[i].p, &a[i].s); sort(a+1,a+1+m); //dp[i][j] = k<si, j>=si, j-k<=li max(dp[i-1][k]+p*(j-k)) (] //dp[i][j] = j-li<=k<=si max(dp[i-1][k]-p*k)+p*j; //dp[i][j] = dp[i-1][j] //dp[i][j] = dp[i][j-1] memset(dp[0],0,sizeof(dp[0])); REPE(i,1,m) { int l=0,r=0; REP(k,max(a[i].s-a[i].l,0), a[i].s) { int t=dp[i-1][k]-a[i].p*k; while(r>l && dw[r-1]<t) r--; dw[r]=t; di[r]=k; r++; } //] int mj=min(a[i].l+a[i].s, n);____ dp[i][0]=0; REPE(j,1,n) { dp[i][j]=max(dp[i][j-1], dp[i-1][j]); if(j>=a[i].s) { int t=j-a[i].l; while(l<r && di[l]<t) l++; if(l<r) dp[i][j]=max(dp[i][j], dw[l]+a[i].p*j); } } } printf("%d\n", dp[m][n]); }