2016.4 半期 DP+单调队列优化 摘橘子

nkoj 3665

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号奶牛不采摘。

分析:

状态  f[i][j]表示前i头牛摘前j棵树的最大值。

s[i]存储每头奶牛

方程:

(1)第[i]头牛不摘 f[i-1][j];

(2)第j棵树不摘 f[i][j-1];

(3) 前i-1头牛处理前k棵树,k+1......j第[i]头牛处理  max{ f[i][k]+s[i].p*(j-k)}    (s[i].like-s[i].L<= k<= s[i].like+s[i].L-1 &&k>=j-s[i].L)

代码如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
const int maxn=30000+5,maxm=100+5;
int f[maxm][maxn],n,m;
struct cow{
	int L,p,like;
	bool operator < (const cow a)const {
		return like<a.like;
	}
}s[maxm];
int main(){
	int i,j,k;
	scanf("%d%d",&n,&m);
	for(i=1;i<=m;i++)
		scanf("%d%d%d",&s[i].L,&s[i].p,&s[i].like);
	sort(s+1,s+1+m);
	for(i=1;i<=m;i++){
		int L=max(0,s[i].like-s[i].L);
		int R=min(n,s[i].like+s[i].L-1);
		for(j=0;j<s[i].like;j++)f[i][j]=f[i-1][j];   //第I头牛不起作用,只有情况(1); 
		for(j=s[i].like;j<=R;j++){        //情况(2)
			f[i][j]=max(f[i-1][j],f[i][j-1]);
			for(k=L;k<s[i].like;k++)
				if(k>=j-s[i].L)
					f[i][j]=max(f[i][j],f[i-1][k]+s[i].p*(j-k));
		}
		for(j=R+1;j<=n;j++)      //第I头牛无法直接摘取,只有情况(1),(3); 
			f[i][j]=max(f[i-1][j],f[i][j-1]);
	}
	printf("%d",f[m][n]);
}


以上动规算法当然是正确的,时间复杂度为O(n*n*m) ,TLE是必需的;
怎么优化呢?
分析之后发现时间主要花在情况(2)上再看看情况(2):
max(f[i-1][k]+p[i]*(j-k)) = max(f[i-1][k]-p[i]*k+p[i]*j) = max(f[i-1][k]-p[i]*k)+ p[i]*j其中p[i]*j对与f[i][j]是固定的,即f[i-1][k]-p[i]*k越大越好
其中(s[i].like-s[i].L<= k<= s[i].like+s[i].L-1 &&k>=j-s[i].L)
所以可以首先将[likei-Li,likei-1]区间的值预处理出来,并在每次选取优先队列中元素时判断它是否满足k+Li>=j即可(如果不满足,因为j是递增的,它以后也不会满足,所以可以直接pop掉)
预处理的方法是单调队列,以下用手工队列实现。
代码如下:

<pre name="code" class="cpp">#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
const int maxn=16000+5,maxm=100+5;
int f[maxm][maxn],q[maxn],qpos[maxn],n,m;
//q[]存储的就是f[i-1][k]-p[i]*k ,qpos[]记下对应的下标;
struct cow{
	int L,p,like;
	bool operator < (const cow a)const {
		return like<a.like;
	}
}s[maxm];
int main(){
	int i,j,k;
	int front,rear;
	scanf("%d%d",&n,&m);
	for(i=1;i<=m;i++)
		scanf("%d%d%d",&s[i].L,&s[i].p,&s[i].like);
	sort(s+1,s+1+m);
	for(i=1;i<=m;i++){
		int L=max(0,s[i].like-s[i].L);
		int R=min(n,s[i].like+s[i].L-1);
		for(j=0;j<s[i].like;j++)f[i][j]=f[i-1][j];   //第I头牛不起作用,只有情况(1); 
		front=rear=1;
		for(k=L;k<s[i].like;k++){     //入队
			int cur=f[i-1][k]-s[i].p*k;
			while(front!=rear&&q[rear-1]<cur)rear--;   //维护单调递减
			q[rear]=cur;qpos[rear]=k;rear++;  
		}
		for(j=s[i].like;j<=R;j++){
			f[i][j]=max(f[i-1][j],f[i][j-1]);
			while(front!=rear&&j-qpos[front]>s[i].L)front++;   //判断k+L_i>=j
			if(front!=rear)
				f[i][j]=max(f[i][j],q[front]+s[i].p*j);
		}
		for(j=R+1;j<=n;j++)      //第I头牛无法直接摘取,只有情况(1),(3); 
			f[i][j]=max(f[i-1][j],f[i][j-1]);
	}
	printf("%d",f[m][n]);
}


 

倾情奉献:详细注释+STL版

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<deque>
using namespace std;
struct node{
	long long l,p,like;
};
bool cmp(node a,node b){
	return a.like<b.like;
}
node cow[105];
long long n,m;
long long f[105][30005];//f[i][j]表示前i头牛来搞前j棵树的最大值 
deque<long long>q;
int main(){
	long long i,j,k,left,right;
	cin>>n>>m;
	for(i=1;i<=m;i++){
		scanf("%I64d%I64d%I64d",&cow[i].l,&cow[i].p,&cow[i].like);
	}
	sort(cow+1,cow+1+m,cmp);//按照每只牛喜欢树的编号来排序 
	for(i=1;i<=m;i++){
		left=max((long long)(0),cow[i].like-cow[i].l);//left表示第i只牛影响区域的左界 
		right=min((long long)(n),cow[i].like+cow[i].l-1);//right表示第i只牛影响区域的右界 
		for(j=0;j<cow[i].like;j++)f[i][j]=f[i-1][j];//左边管不到的地方由前面i-1头牛来搞 
		q.clear();
		for(k=left;k<cow[i].like;k++){
			//动态规划方程:f[i][j]=max(f[i-1][j]+(j-k)*cow[i].p)
			//              k>=cow[i].like-cow[i].l&&k<cow[i].like 
			//此处用单调队列来优化 
			int temp;
			temp=f[i-1][k]-cow[i].p*k;
			while(q.size()&&(f[i-1][q.back()]-cow[i].p*q.back()<temp))q.pop_back();
			q.push_back(k);
		}
		for(j=cow[i].like;j<=right;j++){
			//此处j为奶牛摘桔子的右界 
			while(q.size()&&j-q.front()>cow[i].l)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())*cow[i].p);
		}
		for(j=right+1;j<=n;j++){
			//右边管不了的地方继承前面的状态 
			f[i][j]=max(f[i-1][j],f[i][j-1]);
		}
	}
	cout<<f[m][n];
} 


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值