题目:
数据范围:m<=100 n<=16000
分析:
定义dp[i][j]为第i个人负责前j个单位的最大贡献。
转移:dp[i][j]=max( dp[i][j],dp[i][k]+(j-(k+1)+1)*w[i] )
化简得:dp[i][k]-k*w[i]+j*w[i]
前半部分与k有关,维护一个单调队列,每一次满足条件后就去更新dp[i][j]
单调队列的比较方式是上述式子,而里面存的值是位置
每个人能解决的是一段区间,且必须用起始位置p点,所以先将区间排序(按起点从小到大)
每次枚举一个人,再枚举j:1~n,将1~n分为三段:
1. j小于i的起始位置:也就是无法用i,但可以将这一部分的dp值入队(本来是要枚举k的,这里相当于是在枚举k,入队准备更新j)
2. j在起始位置与其向右最远能覆盖的距离之间:可以用单调队列里面的值来更新j。
3. j超过了其向右的最远覆盖距离:只能继承前面的状态:dp[i-1][j],dp[i][j-1]
注意:
1.每枚举一个点i,就应该把单调队列清空,并把合法的状态先压入队列。
2.在枚举j的时候,记得把超出范围的点排除
#include<bits/stdc++.h> using namespace std; #define M 105 #define N 160005 #define ri register int struct node{ int l,p,s; }a[M]; int dp[M][N],q[N]; bool cmp(const node &a,const node &b) { return a.s<b.s; } int main() { freopen("gift.in", "r", stdin); freopen("gift.out", "w", stdout); int n,m; scanf("%d%d",&n,&m); for(ri i=1;i<=m;++i) scanf("%d%d%d",&a[i].l,&a[i].p,&a[i].s); sort(a+1,a+1+m,cmp); int ans=0,h,t; for(ri i=1;i<=m;++i){ h=1,t=0; q[++t]=max(0,a[i].s-a[i].l);//与0取max是因为0可以入队,也就是说在后面更新其他值的时候 可以由一个也不选更新过来 for(ri j=1;j<=n;++j){ dp[i][j]=max(dp[i-1][j],dp[i][j-1]); if(j >= a[i].s+a[i].l) continue;//不break是为了更新前一行的状态继承 while( h<=t && a[i].l+q[h]<j ) h++;//将无法到达的排除 if(j<a[i].s){//i,j合法 则j一定要在i起始位置之前 这里的j相当于式子里面的k 是可以去更新后续j位置的 int tmp=dp[i-1][j]-a[i].p*j;//式子 while( h<=t && tmp>=dp[i-1][q[t]]-a[i].p*q[t] ) t--;//单调递减的序列 q[++t]=j; continue;//这里只是在将k入队 方便更新后面的j 还没达到这个范围 j不能被更新 } //达到j可以被更新的范围:j>=a[i].s dp[i][j]=max(dp[i][j],dp[i-1][q[h]]+(j-q[h])*a[i].p);//q[h]=k q记录的是位置 通过dp[i][k]-k*w比较 } } printf("%d ",dp[m][n]); } /* 8 4 3 2 2 3 2 3 3 3 5 1 1 7 */