题目:

 

数据范围: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\n",dp[m][n]);
}
/*
8 4
3 2 2
3 2 3
3 3 5
1 1 7
*/
View Code

 

posted on 2019-10-09 10:41  rua-rua-rua  阅读(148)  评论(0编辑  收藏  举报