单调队列优化DP
做了几道前几天多校的单调队列优化DP题目:
hdu 4326 Dragon Ball
http://acm.hdu.edu.cn/showproblem.php?pid=4362
题意:
Sean的到一个地图上边标有什么时刻什么地点会出现龙珠,龙珠在每一时刻出现在同一行里,Sean每一时刻只能去一个龙珠,给出他取龙珠所消耗的能量以及移动所消耗的能量。求他在每个时间段取龙珠的最小能量消耗;
思路:
dp[i][j]表示在i时间取第j个龙珠的最小能量消耗
则有dp[i][j] = min(dp[i - 1][k],abs(p[i - 1][k].pos - p[i][j].pos)) + p[i][j].w;
此方法复杂度为O(m*n^2)会超时
假设如果都是从当前点的左边来的则有:
dp[i][j] = min(dp[i - 1][k] - p[i - 1][k].pos) + p[i][j].pos + p[i][j].w;
而对于dp[i - 1][k] - p[i - 1][k].pos为定值可以用单调队列维护,从右边来的同理;
这里我们主要是是在在对左边排序上的复杂度优化为O(m*nlogn)
话说奇了个怪了,昨天晚上写了很长时间就是不对,今天早上写完就A了。。无语了。。
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <cmath> #include <queue> #include <stack> #include <set> #include <map> #include <string> #define CL(a,num) memset((a),(num),sizeof(a)) #define iabs(x) ((x) > 0 ? (x) : -(x)) #define Min(a,b) (a) > (b)? (b):(a) #define Max(a,b) (a) > (b)? (a):(b) #define ll long long #define inf 0x7f7f7f7f #define MOD 100000007 #define lc l,m,rt<<1 #define rc m + 1,r,rt<<1|1 #define pi acos(-1.0) #define test puts("<------------------->") #define maxn 100007 #define N 1007 #define M 55 using namespace std; struct node { int pos; int w; }p[M][N]; int dp[M][N],q[N]; int n,m,pos; int cmp(node a,node b) { return a.pos < b.pos; } int main() { //freopen("din.txt","r",stdin); int t,i,j,k; scanf("%d",&t); while (t--) { scanf("%d%d%d",&m,&n,&pos); //cout<<m << n << pos << endl; for (i = 1; i <= m; ++i) { for (j = 1; j <= n; ++j) scanf("%d",&p[i][j].pos); } for (i = 1; i <= m; ++i) { for (j = 1; j <= n; ++j) scanf("%d",&p[i][j].w); sort(p[i] + 1,p[i] + 1 + n,cmp);//对坐标排序 } for (i = 1; i <= n; ++i) dp[1][i] = p[1][i].w + abs(pos - p[1][i].pos);//出事化第一行 int front,tail; for (i = 2; i <= m; ++i) { front = 0; tail = -1; for (j = k = 1; j <= n; ++j) { dp[i][j] = inf; while (k <= n && p[i - 1][k].pos <= p[i][j].pos)//单调队列维护j左边取余最小值 { int tmp = dp[i - 1][k] - p[i - 1][k].pos; while (tail >= front && dp[i - 1][q[tail]] - p[i - 1][q[tail]].pos >= tmp) tail--; q[++tail] = k; ++k; } if (tail >= front) dp[i][j] = dp[i - 1][q[front]] + p[i][j].pos - p[i - 1][q[front]].pos + p[i][j].w; } front = 0; tail = -1; for (j = k = n; j >= 1; --j) { while (k >= 1 && p[i - 1][k].pos >= p[i][j].pos)//维护j右边的区间取最大值 { int tmp = dp[i - 1][k] + p[i - 1][k].pos; while (tail >= front && dp[i - 1][q[tail]] + p[i - 1][q[tail]].pos >= tmp) tail--; q[++tail] = k; --k; } if (tail >= front) dp[i][j] = min(dp[i][j],dp[i - 1][q[front]] + p[i - 1][q[front]].pos - p[i][j].pos + p[i][j].w); } } int MIN = inf; for (i = 1; i <= n; ++i) { //printf("%d\n",dp[m][i]); MIN = min(MIN,dp[m][i]); } printf("%d\n",MIN); } return 0; }
hdu 4374 One hundred layer
http://acm.hdu.edu.cn/showproblem.php?pid=4374
和上一道题目类似,不过这里要对单调队列的长度进行一下维护。
题意:
有一个n曾的楼,从第一层往上走,每一层都有m块,可以从对应的本层的第y块跳到上一层的第y块,要求限制每一层只能往左右走,而且走的最大长度为T,问到达第n才层后的最大得分(ps:这里每一块都对应着一个得分,给出n*m矩阵表示得分);
思路:
dp[i][j] = max(dp[i - 1][k] + abs(dis(k,j))) j - T <= k <= j + T;
如果暴力的话时间复杂度为O(n*m^2)超时,这里我们可以从j的左边枚举一下右边枚举一下单调队列维护1-j或者j - n区间的最值,进行dp即可。
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <cmath> #include <queue> #include <stack> #include <set> #include <map> #include <string> #define CL(a,num) memset((a),(num),sizeof(a)) #define iabs(x) ((x) > 0 ? (x) : -(x)) #define Min(a,b) (a) > (b)? (b):(a) #define Max(a,b) (a) > (b)? (a):(b) #define ll long long #define inf 0x7f7f7f7f #define MOD 100000007 #define lc l,m,rt<<1 #define rc m + 1,r,rt<<1|1 #define pi acos(-1.0) #define test puts("<------------------->") #define maxn 100007 #define M 10007 #define N 107 using namespace std; int sum[N][M]; int dp[N][M],q[M]; int n,m,T,pos; int main() { //freopen("din.txt","r",stdin); int i,j; while (~scanf("%d%d%d%d",&n,&m,&pos,&T)) { CL(sum,0); for (i = 1; i <= n; ++i) { for (j = 1; j <= m; ++j) { scanf("%d",&sum[i][j]); sum[i][j] += sum[i][j - 1];//累加求个区间和 dp[i][j] = -inf; } } /*for (i = 1; i <= n; ++i) { for (j = 1; j <= m; ++j) printf("%d ",sum[i][j]); puts(""); }*/ //初始化第一行 for (i = pos; i <= m && i <= pos + T; ++i) dp[1][i] = sum[1][i] - sum[1][pos - 1]; for (i = pos - 1; i >= 1 && i >= pos - T; --i) dp[1][i] = sum[1][pos] - sum[1][i - 1]; /*for (i = 1; i <= m; ++i) printf("%d ",dp[1][i]); puts("");*/ int front,tail; for (i = 2; i <= n; ++i) { //左扫 front = 0,tail = -1; for (j = 1; j <= m; ++j) { while (tail >= front && j - q[front] > T) front++;//维护单调队列的长度 if (dp[i - 1][j] != -inf)//因为这里上一层可能存在不能到达的点也就不能更新下一层 { int tmp = dp[i - 1][j] + sum[i][j] - sum[i][j - 1]; while (tail >= front && dp[i - 1][q[tail]] + sum[i][j] - sum[i][q[tail] - 1] < tmp) tail--;//单调队列里维护的是到j - 1分值最高的的对于j来说也是1到j-1点里面到达他的分值最高的的,自己yy吧 q[++tail] = j; } if (tail >= front) dp[i][j] = dp[i - 1][q[front]] + sum[i][j] - sum[i][q[front] - 1]; } //右扫 front = 0,tail = -1; for (j = m; j >= 1; --j) { while (tail >= front && q[front] - j > T) front++; if (dp[i - 1][j] != -inf) { int tmp = dp[i - 1][j] + sum[i][j] - sum[i][j - 1]; while (tail >= front && dp[i - 1][q[tail]] + sum[i][q[tail]] - sum[i][j - 1] < tmp) tail--; q[++tail] = j; } if (tail >= front) dp[i][j] = max(dp[i][j],dp[i - 1][q[front]] + sum[i][q[front]] - sum[i][j - 1]); } } int MAX = -inf; for (i = 1; i <= m; ++i) { // cout<<">>"<<dp[n][i]<<endl; MAX = max(MAX,dp[n][i]); } printf("%d\n",MAX); } return 0; }
hdu 3401 Trade
http://acm.hdu.edu.cn/showproblem.php?pid=3401
题意:
给出T天的买卖股票状况,在第i天买一股的费用为ap[i],卖一股的费用为bp[i],而且两次交易(买或者卖)的时间差必须大于w天,规定一天最多买卖maxp股股票。给出所有数据求解最后lxhgww 能赚得的最多的钱。
思路:
动态规划,每一天都有三种情况,不买不卖,买或者卖
状态转移方程是:
对于买
dp[i][j] = max (dp[i][j], dp[r][k] - (j - k) * ap[i]) 1 <= r <= i - w -1 , max(0, j - as[i]) <= k < j
对于卖
dp[i][j] = max (dp[i][j], dp[r][k] + (j - k) * ap[i]) 1 <= r <= i - w -1 , min(maxp, j + bs[i]) >k > j
总的时间复杂度是0(maxp*maxp*T*T),一定要超时,要降低复杂度
首先bp[i]>=ap[i]所以对于同一天的商品只能买如果卖的话指定会赔本或者不变。前w天的的商品之间不会进行转移只会不买不卖的传递。
首先对于每一天,枚举下j做一次
dp[i][j] = max(dp[i][j],dp[i-1][j])
这样就可以保证对于dp[i][j],dp[i-w-1][]已经包含了前面的最好情况
不用再枚举上次交易是哪一天 1 <= k <= i - w -1这一维了
时间复杂度便成为0(maxp*maxp*T),不过还是不行
然后转移的时候:(只讨论买的情况)
dp[i-1][r]-(j-r)*ap[i]
即 dp[i-1][r]-j*ap[i]+r*ap[i]
外层循环 i,j 必要
则i j确定 ==> j*ap[i] 确定
最优的是dp[i-1][r]+r*ap[i]最优(只与上层有关)
每次i,j循环都要计算但是我们可以把每个r对应的值算出来维护个单调队列
卖一样;
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <cmath> #include <queue> #include <stack> #include <set> #include <map> #include <string> #define CL(a,num) memset((a),(num),sizeof(a)) #define iabs(x) ((x) > 0 ? (x) : -(x)) #define Min(a,b) (a) > (b)? (b):(a) #define Max(a,b) (a) > (b)? (a):(b) #define ll long long #define inf 0x7f7f7f7f #define MOD 100000007 #define lc l,m,rt<<1 #define rc m + 1,r,rt<<1|1 #define pi acos(-1.0) #define test puts("<------------------->") #define maxn 100007 #define M 10007 #define N 2007 using namespace std; int dp[N][N]; int ap[N],bp[N],as[N],bs[N]; int maxp,T,w; int q[N]; int main() { //freopen("din.txt","r",stdin); int t,i,j; scanf("%d",&t); while (t--) { scanf("%d%d%d",&T,&maxp,&w); for (i = 1; i <= T; ++i) scanf("%d%d%d%d",&ap[i],&bp[i],&as[i],&bs[i]); for (i = 1; i <= T; ++i) for (j = 0; j <= maxp; ++j) dp[i][j] = -inf;//初始化 for (i = 1; i <= w + 1; ++i)//前w天买 { for (j = 0; j <= min(maxp,as[i]); ++j) { dp[i][j] = -ap[i]*j; } } int front,tail; for (i = 2; i <= T; ++i) { for (j = 0; j <= maxp; ++j)//保证dp[i - w - 1][j]是最优的 { dp[i][j] = max(dp[i][j],dp[i - 1][j]); } if (i < w + 2) continue; //买 front = 0; tail = -1; for (j = 0; j <= maxp; ++j) { while (tail >= front && q[front] + as[i] < j) front++;//单调队列维护长度不能超过as int tmp = dp[i - w - 1][j]; while (tail >= front && dp[i - w - 1][q[tail]] - ap[i]*(j - q[tail]) < tmp) tail--; q[++tail] = j; if (tail >= front) dp[i][j] = max(dp[i][j],dp[i - w - 1][q[front]] - ap[i]*(j - q[front])); } //卖 front = 0; tail = -1; for (j = maxp; j >= 0; --j) { while (tail >= front && q[front] - bs[i] > j) front++; int tmp = dp[i - w - 1][j]; while (tail >= front && dp[i - w - 1][q[tail]] + bp[i]*(q[tail] - j) < tmp) tail--; q[++tail] = j; if (tail >= front) dp[i][j] = max(dp[i][j],dp[i - w - 1][q[front]] + bp[i]*(q[front] - j)); } } int MAX = -inf; for (i = 0; i <= maxp; ++i) { //cout<<dp[T][i]<<endl; MAX = max(MAX,dp[T][i]); } printf("%d\n",MAX); } return 0; }
hdu 3905 Sleeping
http://acm.hdu.edu.cn/showproblem.php?pid=3905
题意:
ZZZ上一堂课有N分钟,给出每分钟他听课的话能够得到的学分,并且规定在这N分钟里他必须休息至少m分钟,而且如果他听课必须听连续的长度为L分钟的课,问它能够获得的最多的学分。
思路:
dp[i][j] = max(dp[i -1][j - 1],dp[k][j] + sum[i] - sum[k]) dp[i][j]表示前i分钟休息了j分钟所获得的最大得分。如果正常递推的话是O(n^3)肯定会超时,由于k属于[j,i - L]一个区间,只要维护区间最值即可 ,于是我想到了优先队列,可是如果for(i:n) for(j:m) 推得话,dp[k][j] - sum[k]这里在单调队列里面的dp[k][j]就会变化,而不是我们想要的了,于是就纠结啊纠结。其实这里只要将循环互换,for(j:m)for (i:m)这样每一层之和上一层有关了,而且每一层的j都是相同的,这样就保证了我们单调队列里面的值不会改变。哎,这样的优化题目还是不熟啊。
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <cmath> #include <queue> #include <stack> #include <set> #include <map> #include <string> #define CL(a,num) memset((a),(num),sizeof(a)) #define iabs(x) ((x) > 0 ? (x) : -(x)) #define Min(a , b) ((a) < (b) ? (a) : (b)) #define Max(a , b) ((a) > (b) ? (a) : (b)) #define ll __int64 #define inf 0x7f7f7f7f #define MOD 100000007 #define lc l,m,rt<<1 #define rc m + 1,r,rt<<1|1 #define pi acos(-1.0) #define test puts("<------------------->") #define maxn 100007 #define M 1007 #define N 1007 using namespace std; //freopen("din.txt","r",stdin); int dp[N][M]; int q[M],sum[N]; int Get(){ char ch = getchar(); while (ch < '0' || ch > '9') ch = getchar(); int num = 0; while (ch >= '0' && ch <= '9'){ num = num*10 + ch - '0'; ch = getchar(); } return num; } int main(){ int i,j; int n,m,L; while (~scanf("%d%d%d",&n,&m,&L)){ sum[0] = 0; for (i = 1; i <= n; ++i){ sum[i] = Get(); sum[i] += sum[i - 1]; } CL(dp,0); for (i = L; i <= n; ++i) dp[i][0] = sum[i]; int fr,tl; int k; //循环倒置关键 for (j = 1; j <= m; ++j){ k = j; fr = 0; tl = 0; for (i = L; i <= n; ++i){ dp[i][j] = dp[i - 1][j - 1]; while (k >= j && k <= i - L){ while (fr <= tl && dp[q[tl]][j] - sum[q[tl]] <= dp[k][j] - sum[k]) tl--; q[++tl] = k; ++k; } if (fr <= tl) dp[i][j] = max(dp[i][j],dp[q[fr]][j] + sum[i] - sum[q[fr]]); } } printf("%d\n",dp[n][m]); } return 0; }