题目链接
Time Limit: 1000MS | Memory Limit: 30000K | |
Total Submissions: 3604 | Accepted: 1094 |
Description
Input
Output
Sample Input
8 4 3 2 2 3 2 3 3 3 5 1 1 7
Sample Output
17
Hint
题意:长度为n的墙,k个粉刷匠。第 i 个粉刷匠在 si ,他最多可以刷包含 si 的长度为 li 的区间,他刷单位长度获得钱 pi 。求k个粉刷匠最多能赚多少钱?
题解:首先将k个人按所在位置排序 。dp[ i ][ j ] 表示前i个人,处理了前j 的墙的最大收益(处理表示:可以刷,可以不刷)。转移就是:
dp[ i ][ j ] =max( dp[ i ][ j-1 ] , dp[ i -1][ j ] );
dp[ i ][ j ]=max( dp[ i ][ j ] , dp[ i-1 ][ k ]+w(k+1,j,i ) ), j-l[ i ]<=k<s[ i ] 。 w(k+1,j,i)表示第i个人刷 k+1到 j 的收益。
w(k+1,j,i)=p[ i ]*j-p[ i ]*k;
所以第二个转移可以写成:
dp[ i ][ j ]=max(dp[ i ][ j ] ,max(dp[ i-1][ k ]-p[ i ]*k )+p[ i ]*j );
因为:j-l[ i ]<=k<s[ i ]
随着 j 的增大,k的下界在不断增大,上界不变。所以对于max(dp[ i -1][ k ]-p[ i ]*k),我们可以用单调队列维护。这样转移可以降到O(1),复杂度O(n*k)。
代码如下:
#include<stdio.h> #include<algorithm> #include<queue> #include<stack> #include<map> #include<set> #include<vector> #include<iostream> #include<string.h> #include<string> #include<math.h> #include<stdlib.h> #define inff 0x3fffffff #define eps 1e-8 #define nn 21000 #define mod 1000000007 typedef long long LL; const LL inf64=LL(inff)*inff; using namespace std; int n,k; int l[nn],p[nn],s[nn]; int dp[110][nn]; int que[nn]; int l1,r1; void add(int id,int i) { while(r1>l1) { if(dp[i-1][que[r1-1]]-p[i]*que[r1-1]<=dp[i-1][id]-p[i]*id) { r1--; } else { break; } } que[r1++]=id; } void dele(int id) { while(l1<r1) { if(que[l1]<id) { l1++; } else break; } } int main() { int i,j; while(scanf("%d%d",&n,&k)!=EOF) { for(i=1;i<=k;i++) { scanf("%d%d%d",&l[i],&p[i],&s[i]); } for(i=1;i<=k;i++) { for(j=i+1;j<=k;j++) { if(s[j]<s[i]) { swap(l[i],l[j]); swap(s[i],s[j]); swap(p[i],p[j]); } } } for(i=0;i<=n;i++) dp[0][i]=0; int ix; for(i=1;i<=k;i++) { l1=r1=0; add(0,i); for(j=1;j<=n;j++) { dp[i][j]=max(dp[i][j-1],dp[i-1][j]); if(j>=s[i]) { ix=max(0,j-l[i]); if(ix<s[i]) { dele(ix); dp[i][j]=max(dp[i][j],dp[i-1][que[l1]]-p[i]*que[l1]+p[i]*j); } } if(j<s[i]) add(j,i); } } printf("%d\n",dp[k][n]); } return 0; }