【题意】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; }