NKOI半期 3665 摘橘子

摘橘子

Time Limit:20000MS  Memory Limit:65536K
Total Submit:1 Accepted:1 
Case Time Limit:1000MS

Description

约翰在农场上种了一排共N棵橘子树,约翰将它们编号1到N。又到了橘子成熟的季节,约翰安排他的M头奶牛去摘橘子。但每头奶牛都有自己独特的采摘习惯: 
第i头奶牛最多只愿意摘连续Xi棵橘子树上的橘子,并且其中要包括它最喜欢的第Zi号橘子树(它也可以一棵树都不摘)。 
第i头奶牛每摘一棵树,都会从该树上摘下恰好Yi个橘子(每棵树上的橘子都足够多)。 
奶牛不愿采摘其它牛摘过的树,所以一棵树最多只能被一只牛采摘。 
约翰想知道,怎样安排采摘工作才能使得摘下的橘子总数尽可能多。

Input

第一行,两个整数N和M,表示橘子的数量和奶牛的数量 
接下来M行,每行代表一头奶牛,其中第i行的三个整数Xi, Yi, Zi,描述i号奶牛的采摘习惯

Output

一行,一个整数,表示最多能摘下的橘子总数

Sample Input

样例输入1:
8 4
3 2 2
3 2 3
3 3 5
1 1 7 

样例输入2:
1 2
13 11 1
1 15 1

Sample Output

样例输出1:
17

样例输出2:
15

Hint

对于50%的数据: 1<=M<=100 1<=N<=1000 1<= Yi<=10000 1<=Xi<=N 
对于100%的数据: 1<=M<=100 1<=N<=30000 1<= Yi<=10000 1<=Xi<=N 
样例1说明: 
1号奶牛采摘第1到2棵树 
2号奶牛采摘第3到4棵树 
3号奶牛采摘第5到7棵树 
4号奶牛不采摘。

Source

改编自POJ1821


z为喜欢的树的编号

x为工作最大长度

y为各自收益

用f[i][j]表示前i只奶牛处理前j个树的最大收益

因此有三种决策:

1.不用第i头奶牛,即f[i][j]=f[i-1][j]

2.不用第j棵树,f[i][j]=f[i][j-1]

3.前i-1只处理k棵,第i只处理第k+1到j棵,状态转移方程为:f[i][j]=max(f[i-1][k]+y[i]*(j-k))  k<z&&k>=j-x

容易得出50分的暴力dp代码:

#include<iostream>
#include<cstdio>
#include<deque>
#include<algorithm>
using namespace std;
struct node{int L,P,S;}Cow[105];
int dp[105][16005];
deque<int>Q;
bool cmp(node a,node b){return a.S<b.S;}
int main(){
    int i,j,k,n,m,tmp,Left,Right;
    scanf("%d%d",&n,&m);
    for(i=1;i<=m;i++)scanf("%d%d%d",&Cow[i].L,&Cow[i].P,&Cow[i].S);
    sort(Cow+1,Cow+m+1,cmp);
    for(i=1;i<=m;i++){
        Left=max(0,Cow[i].S-Cow[i].L);
        Right=min(n,Cow[i].S+Cow[i].L-1);
        for(j=0;j<Cow[i].S;j++)dp[i][j]=dp[i-1][j];
        for(j=Cow[i].S;j<=Right;j++){
            dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
            for(k=Left;k<Cow[i].S;k++)
                if(k>=j-Cow[i].L)dp[i][j]=max(dp[i][j],dp[i-1][k]+(j-k)*Cow[i].P);
        }
        for(j=Right+1;j<=n;j++)dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
    }
    printf("%d\n",dp[m][n]);
}

因此,j*y[i]是相对固定的,因此在一个区间上求f[i-1][k]-y[i]*k的最大值,应该用单调队列的滑动窗口模型

#include<iostream>
#include<cstdio>
#include<deque>
#include<algorithm>
using namespace std;
struct wk{int x,y,z;}s[105];
int f[105][16005];
deque<int>q;
bool cmp(wk a,wk b){return a.z<b.z;}
int main(){
    int i,j,k,n,m,tmp,l,r;
    scanf("%d%d",&n,&m);
    for(i=1;i<=m;i++)
	    scanf("%d%d%d",&s[i].x,&s[i].y,&s[i].z);
    sort(s+1,s+m+1,cmp);
    for(i=1;i<=m;i++){
        l=max(0,s[i].z-s[i].x);
        r=min(n,s[i].z+s[i].x-1);
        for(j=0;j<s[i].z;j++)
	        f[i][j]=f[i-1][j];
        q.clear();
        for(k=l;k<s[i].z;k++){
            tmp=f[i-1][k]-k*s[i].y;
            while(!q.empty()&&f[i-1][q.back()]-q.back()*s[i].y<tmp)q.pop_back();
            q.push_back(k);
        }
        for(j=s[i].z;j<=r;j++){
            while(!q.empty()&&j-q.front()>s[i].x)q.pop_front();
            f[i][j]=max(f[i-1][j],f[i][j-1]);
            f[i][j]=max(f[i][j],f[i-1][q.front()]+(j-q.front())*s[i].y);
        }
        for(j=r+1;j<=n;j++)
		    f[i][j]=max(f[i-1][j],f[i][j-1]);
    }
    printf("%d",f[m][n]);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值