【题意】K个人对N块木板涂色,每个人初始站在一块木板前(不重复),每人最多只能涂包含所站木板的连续l个木板或一个木板也不涂。给出每人最多涂的木块数l,涂一快木板的工钱p,站的木板s。求这群人最多共获得多少工钱。
【分析】dp[i][j]表示前i个人对前j块木板操作的最大收益。
核心状态转移方程:dp[i][j]=max(dp[i][j-1],dp[i-1][k]+P[i].p*(j-k),dp[i-1][j]),max(0,P[i].s-P[i].l)<=k<min(P[i].s-1,j)
显然直接做就是枚举i,j,k。那么这里显然是TLE,特别要踢到的是事刚刚的转移里面k的范围的边界一定要确定好,不然等着WA!
现在来推导一下单调队列优化的核心式子,dp[i][j]=max(dp[i-1][k]+p[i]*p*(j-k)),展开后边,并且把p[i].p*j移到方程左边来,那么这个方程变成了dp[i][j]-j*p[i].p=max(dp[i-1][k]-k*p[i].p),那么这里显而易见了,在枚举k的时候,P[i].p*j的值已经确定不用考虑,只需要找出使(dp[i-1][k]-P[i].p*k)最大的k就行了,又观察到这个式子的值不与j相关,也就是说在枚举k之前我们就可以通过某种方法找出这个k,即:构造递减的 单调队列 维护k值。当然还是有个小坑的,坑了我一下午!
【ps】该去赤饭了~~~
//you are my sunshine.
#include <set>
#include <map>
#include <queue>
#include <vector>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 16050;
const int maxm = 150;
int dp[maxm][maxn];
int que[maxn];
struct node{
int l,p,s;//l每个最多涂的木板数,p涂一块木板需要多少钱,s一个人所在的木板
node(){}
node(int l,int p,int s):l(l),p(p),s(s){}
bool operator<(const node &rhs)const{
return s<rhs.s;
}
}p[maxn];
int main(){
int N,K;
while(~scanf("%d%d",&N,&K)){
for(int i=1; i<=K; i++){
scanf("%d%d%d",&p[i].l,&p[i].p,&p[i].s);
}
sort(p+1,p+K+1);
//INIT
memset(dp,0,sizeof(dp));
int head,tail;
//DP.
for(int i=1; i<=K; i++){
head=0,tail=1;
que[head] = (p[i].s-p[i].l)>0?(p[i].s-p[i].l):0;
for(int j=1; j<=N; j++){
dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
if(j>=p[i].s+p[i].l) continue; //涂不了
//maintain queue.
while(head<tail&&que[head]+p[i].l<j) head++; //队列中的k过小
if(j<p[i].s) //符合k的取值范围可以考虑入队
{
int temp=dp[i-1][j]-j*p[i].p;
while(head<tail&&dp[i-1][que[tail-1]]-que[tail-1]*p[i].p<temp) tail--;
que[tail++] = j;
continue;//优化的前提是第i个人不可以涂这些模板,坑啊,这里我TLE了很久很久!!!
}
dp[i][j] = max(dp[i][j],dp[i-1][que[head]]+p[i].p*(j-que[head]));
}
}
printf("%d\n",dp[K][N]);
}
return 0;
}