您的位置:首页 > 其它

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

2016-04-21 16:43 295 查看
nkoj 3665Description约翰在农场上种了一排共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]
);
}
以上动规算法当然是正确的,时间复杂度为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>=jif(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]);}
倾情奉献:详细注释+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];} 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: