用单调队列优化形如 dp[i][j] = max(dp[i - 1][k] + (j - k) * a[i]) 的状态转移
上式可化为dp[i][j] = max(dp[i - 1][k] - k * a[i]) + j * a[i],
在不用单调队列优化前,对于dp[i][j] 和dp[i][j + 1]枚举 k 时发现,有重复的部分,而且都是取dp[i - 1][...k...]...的最大值或最小值,
用优先队列装着dp[i - 1][k] - k*a[i],在 i 确定,枚举 j 的过程中,每枚举到一个j都会有对应的dp[i - 1][j] - j*a[i] 加入优先队列,
这样对于不同的 j 我们就不用每次从头枚举k了,每次只需从队首取出元素
注意下面几点:
1)取队首元素时,在队首的(k的范围)不符合条件的元素出队,直到取到符合条件的元素,用于更新dp[ i ][ j ]
2)加入元素时(以队首最大的队列为例),从尾部开始,小于当前入队元素的队中元素删掉,将入队元素放到队尾
3)每个元素入队一次,出队一次,相当于在第二层(枚举j)时几乎没增加复杂度,这样我们就将3层循环化为了2层
题目:http://acm.hdu.edu.cn/showproblem.php?pid=2191
题意:多重背包
思路:数据范围不大,可以用一般多重背包的方法先二进制优化,转成01背包做。但在这里学习一下单调队列优化的方法。
本来的状态转移方程是dp[j] = max(dp[j - wi] +vi], dp[j])
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#define MOD 1000000007
#define INF 0x7fffffff
using namespace std;
typedef long long ll;
int w[105], v[105], n[105];
int dp[105], Q[105], P[105];
int main()
{
#ifdef LOCAL
freopen("dpdata.txt", "r", stdin);
#endif
int cas, N, W;
scanf("%d", &cas);
while(cas--)
{
scanf("%d%d", &W, &N);
for(int i = 1; i <= N; i++)
{
scanf("%d%d%d", &w[i], &v[i], &n[i]);
}
memset(dp, 0, sizeof(dp));
for(int i = 1; i <= N; i++)
{
for(int a = 0; a < w[i]; a++)
{
int s = 0, t = 0;
Q[0] = 0; P[0] = 0;
for(int j = 0; j * w[i] + a <= W; j++)
{
int temp = dp[j * w[i] + a] - j * v[i];
while(s <= t && Q[t] <= temp) t--;
Q[++t] = temp;
P[t] = j;
while(s <= t && P[s] < j - n[i]) s++;
dp[j * w[i] + a] = Q[s] + j * v[i];
}
}
}
printf("%d\n", dp[W]);
}
return 0;
}
题意:买卖同一个股票,给出t天,每天可以用ap的价格买一份,bp的价格卖一份,每天最多买as份,卖bs份,问t天后最多能赚多少钱。两次交易需间隔w天。股票的价值不用考虑。
思路:
dp[ i ][ j ] 记录第 i 天后持有 j 份股票时最多赚了多少钱
不买不卖 dp[i][j] = max(dp[i][j], dp[i - 1][j])
买dp[i][j] = max(dp[i - w - 1][k] - (j - k) * ap[i], dp[i][j])
卖dp[i][j] = max(dp[i - w - 1][k] + (k - j) * bp[i], dp[i][j])
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#define MOD 1000000007
#define INF 50000000
using namespace std;
typedef long long ll;
int ap[2005], bp[2005], as[2005], bs[2005];
int dp[2005][2005], Q[2005], P[2005];//Q记录值,P记录下标k
int main()
{
#ifdef LOCAL
freopen("dpdata.txt", "r", stdin);
#endif
int cas, n, maxp, w;
scanf("%d", &cas);
while(cas--)
{
scanf("%d%d%d", &n, &maxp, &w);
for(int i = 1; i <= n; i++)
{
scanf("%d%d%d%d", &ap[i], &bp[i], &as[i], &bs[i]);
}
for(int i = 0; i <= n; i++)
{
for(int j = 0; j <= maxp; j++)
dp[i][j] = -INF;
}
dp[0][0] = 0;
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= min(maxp, as[i]); j++)
dp[i][j] = - j * ap[i];
}
for(int i = 1; i <= n; i++)
{
/*本来的状态转移,3个循环,在此基础上更改
for(int j = 0; j <= maxp; j++)
for(int k = max(0, j - as[i]); k <= j; k++)
{
dp[i][j] = max(dp[i - w - 1][k] - (j - k) * ap[i], dp[i][j]);
}
for(int k = j; k <= min(j + bs[i], maxp); k++)
{
dp[i][j] = max(dp[i - w - 1][k] + (k - j) * bp[i], dp[i][j]);
}*/
for(int j = 0; j <= maxp; j++)//不买不卖
dp[i][j] = max(dp[i][j], dp[i - 1][j]);
if(i - w - 1 <= 0) continue;
int s = 0, t = -1;
for(int j = 0; j <= maxp; j++)
{ //买
int temp = dp[i - w - 1][j] + j * ap[i];//从队尾找位置加入元素,同时删除多余元素
while(s <= t && Q[t] <= temp) t--;
Q[++t] = temp; P[t] = j;
while(s <= t && j - P[s] > as[i]) s++;//删除k范围不符合的元素
dp[i][j] = max(Q[s] - j * ap[i] ,dp[i][j]);
}
s = 0; t = -1;
for(int j = maxp; j >= 0; j--)
{ //卖
int temp = dp[i - w - 1][j] + j * bp[i];
while(s <= t && Q[t] <= temp) t--;
Q[++t] = temp; P[t] = j;
while(s <= t && P[s] - j > bs[i]) s++;
dp[i][j] = max(Q[s] - j * bp[i], dp[i][j]);
}
//printf("dp[%d][%d]=%d\n", i, j, dp[i][j]);
}
int ans = 0;
for(int i = 0; i <= maxp; i++)
ans = max(ans, dp[n][i]);
printf("%d\n", ans);
}
return 0;
}
题目:http://poj.org/problem?id=1821
题意:栅栏长n个单位,有k个工人,每个工人可刷连续长度为Li,没刷一个长度赚钱Pi,他们站在固定的位置Si(不重复),问这k个工人最多一共赚多少钱。每个工人如果刷的话,必刷了自己所在的位置。
思路:
先将工人按所在位置排序,dp[ i ][ j ] 表示第i个工人刷完,刷到前 j 个板最多赚多少钱。
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#define MOD 1000000007
#define INF 0x7fffffff
using namespace std;
typedef long long ll;
int dp[110][16010];
int Q[16010], P[16010];//Q记录值,P记录下标k
struct Worker
{
int l, p, s;
}wk[105];
int cmp(Worker a, Worker b)
{
return a.s < b.s;
}
int main()
{
#ifdef LOCAL
freopen("dpdata.txt", "r", stdin);
#endif
int m, n;
while(~scanf("%d%d", &m, &n))
{
memset(wk, 0, sizeof(wk));
for(int i = 1; i <= n; i++)
{
scanf("%d%d%d", &wk[i].l, &wk[i].p, &wk[i].s);
}
sort(wk + 1, wk + n + 1, cmp);
memset(dp, 0, sizeof(dp));
for(int i = 1; i <= n; i++)
{
dp[i][0] = 0;
int s = 0, t = 0;
Q[0] = 0; P[0] = 0;
for(int j = 1; j <= m; j++)
{
/*原状态转移
for(int k = j - wk[i].l; k <= j; k++)
{
dp[i][j] = max(dp[i - 1][k] + (j - k) * wk[i].p, dp[i][j]);
}*/
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);//第i个工人不刷j或第j格没被刷
if(wk[i].s - wk[i].l <= j && j < wk[i].s)
{//j为工人i刷时可能的开始位置
int temp = dp[i - 1][j] - j * wk[i].p;
while(s <= t && Q[t] <= temp) t--;
Q[++t] = temp;
P[t] = j;
}
if(wk[i].s <= j && j < wk[i].s + wk[i].l)
{ //j为工人i刷时可能的结束位置
while(s <= t && P[s] + wk[i].l < j) s++;
dp[i][j] = max(Q[s] + j * wk[i].p, dp[i][j]);
}
}
}
printf("%d\n", dp[n][m]);
}
return 0;
}