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];
}