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