DP[用单调性优化][专辑]

一、学习

1.sha崽

2.几篇论文

 

二、练习题

1.Sliding Window [pku-2823]

分析:单调队列入门级别的题目。单调队列比普通队列多了一个操作---队尾删除。队列元素有两个域,<index,value>。由于元素是按index(下标)从小到大插入的,所以index在队列中保持单调递增;为保持value的单调性,可以在队尾插入时实现。而队首元素就是所需要的最小值(或最大值),每个元素最多进队一次,出队一次,复杂度为O(n)。

 

pku-2823
#include <stdio.h>
#include
<string.h>
#include
<stdlib.h>
#include
<ctype.h>
#include
<math.h>
#include
<set>
#include
<map>
#include
<queue>
#include
<vector>
#include
<algorithm>

using namespace std;

//#define SUPERBIN
#define NL1 1000005
#define NL2 21
#define EP 1e-10
#define MAX(x,y) ((x)>(y)?(x):(y))
#define MIN(x,y) ((x)<(y)?(x):(y))
#define LL long long

int d[NL1], stk[NL1], stk1[NL1], dt[NL1];
int n, k;

int main() {
#ifdef SUPERBIN
freopen(
"in.txt", "r", stdin);
freopen(
"out.txt", "w", stdout);
#endif
int T, t, r, cs = 1;
int i, j, k, a, b, c, x, y, z;
int front, tail;
int front1, tail1;
while (scanf("%d%d", &n, &k) != EOF) {
front
= tail = 0;
front1
= tail1 = 0;
k
= k > n ? n : k;
for (i = 0; i < n; i++) scanf("%d", &dt[i]);
for (i = 0; i < k - 1; i++) {
while (tail > front && dt[stk[tail - 1]] >= dt[i]) tail--;
while (tail1 > front1 && dt[stk1[tail1 - 1]] <= dt[i]) tail1--;
stk[tail
++] = i;
stk1[tail1
++] = i;
}
j
= 0;
x
= i;
while (i < n) {
while (tail > front && dt[stk[tail - 1]] >= dt[i]) tail--;
stk[tail
++] = i;
if (stk[front] < i - k + 1) front++;
d[i]
= stk[front];
i
++;
}
for (i = k-1; i < n; i++)
printf(
"%d%c", dt[d[i]], i == n - 1 ? '\n' : ' ');
j
= 0;
i
= x;
while (i < n) {
while (tail1 > front1 && dt[stk1[tail1 - 1]] <= dt[i]) tail1--;
stk1[tail1
++] = i;
if (stk1[front1] < i - k + 1) front1++;
d[i]
= stk1[front1];
i
++;
}
for (i = k-1; i < n; i++)
printf(
"%d%c", dt[d[i]], i == n - 1 ? '\n' : ' ');
}
return 0;
}
/*
* DP+单调队列优化
*/

 

 

2.Max Sum of Max-K-sub-sequence  [hdu-3415]

分析:令s[i]为前i个元素的和,序列长度增加K,DP方程为:d[i] = MAX(s[i]-s[j]),(i-j<=K) 其中s[i]-s[j]表示序列[j-1,i]的和。利用单调队列维护s[j]。

 

hdu-3415
#include <stdio.h>
#include
<string.h>
#include
<stdlib.h>
#include
<ctype.h>
#include
<math.h>
#include
<set>
#include
<map>
#include
<queue>
#include
<vector>
#include
<algorithm>

using namespace std;

//#define SUPERBIN
#define NL1 100004
#define NL2 NL1*2
#define EP 1e-10
#define MAX(x,y) ((x)>(y)?(x):(y))
#define MIN(x,y) ((x)<(y)?(x):(y))
#define LL long long

int d[NL2], st[NL2], w[NL2], sum[NL2];
int n, k;

int main() {
#ifdef SUPERBIN
freopen(
"in.txt", "r", stdin);
freopen(
"out.txt", "w", stdout);
#endif
int T, t, r, cs = 1;
int i, j, a, b, c, x, y, z, n1;
scanf(
"%d", &T);
while (T--) {
scanf(
"%d%d", &n, &k);
n1
=n;
for (i = 1; i <= n; i++) scanf("%d", &w[i]);
n
+= k;
for (j = 1; i <= n; i++, j++) w[i] = w[j];
for (i = 1,sum[0] = 0; i <= n; i++) {
sum[i]
= sum[i - 1] + w[i];
}
int front, rear;
front
= rear = 0;
i
= 0;
int mx, mxl, mxr;
mx
= -0xfffffff;
while (i < n) {
while (front < rear && sum[st[rear - 1]] >= sum[i]) rear--;
st[rear
++] = i;
while (front < rear && i + 1 - st[front] > k) front++;
x
= sum[i + 1] - sum[st[front]];
if (x > mx) {
mx
= x;
mxl
= st[front]+1; //key
mxr = i + 1;
}
i
++;
}
printf(
"%d %d %d\n", mx, mxl, mxr>n1?mxr-n1:mxr);
}

return 0;
}

 

 

3.Trade [hdu-3401]

分析:d[i][j]表示在第i天持有股票j的最大收益。根据三种决策(买,卖,nothing)往前推。

d[i][j] = MAX( d[i-W-1][k] - AP[i]*(j-k) ) (j-k<=AS[i]),[在第 i 天购买 j-k 支股 得到 j 支股]

d[i][j] = MAX( d[i-W-1][k] + BP[i]*(k-j) ) (k-j<=Bs[i]),[在第 i 天卖出 k-j 支股 得到 j 支股]

d[i][j] = MAX( d[i][j], d[i-1][j] ),[在第 i 天不买也不卖]

前两个方程的决策 k 可以用单调队列进行优化,只须稍加变形:

d[i][j] = MAX(d[i-W-1][k] + AP[i]*k) - AP[i]*j ;

d[i][j] = MAX(d[i-W-1][k] + BP[i]*k) - BP[i]*j .

 

hdu-3401
#include <stdio.h>
#include
<string.h>
#include
<stdlib.h>
#include
<ctype.h>
#include
<math.h>
#include
<set>
#include
<map>
#include
<queue>
#include
<vector>
#include
<algorithm>

using namespace std;

//#define SUPERBIN
#define NL1 2011
#define NL2 21
#define EP 1e-10
#define MAX(x,y) ((x)>(y)?(x):(y))
#define MIN(x,y) ((x)<(y)?(x):(y))
#define LL long long

int d[NL1][NL1], AP, BP, AS, BS;
int st1[NL1][2], front1, rear1;
int W, T, MaxP;

int main() {
#ifdef SUPERBIN
freopen(
"in.txt", "r", stdin);
freopen(
"out.txt", "w", stdout);
#endif
int t, i, j, k, a, b, c, x, y, z;
scanf(
"%d", &t);
while (t--) {
scanf(
"%d%d%d", &T, &MaxP, &W);
for (i = 1; i <= T; i++) {
scanf(
"%d%d%d%d", &AP, &BP, &AS, &BS);
for (j = 0; j <= MaxP; j++) d[i][j] = -0x7fffffff;
if (i <= W + 1) {
for (j = 0; j <= MaxP && j <= AS; j++) {
d[i][j]
= -AP*j;
}
}
if (i > 1) { //key
for (j = 0; j <= MaxP; j++)
d[i][j]
= MAX(d[i][j], d[i - 1][j]);
}
if (i <= W + 1) continue;

front1
= rear1 = 0;
for (j = 0; j <= MaxP; j++) {
k
= j >= AS ? (j - AS) : 0;
x
= d[i - W - 1][j] + AP * j;
while (front1 < rear1 && st1[rear1 - 1][1] <= x) rear1--;
st1[rear1][
0] = j;
st1[rear1
++][1] = x;
while (front1 < rear1 && st1[front1][0] < k) front1++;

d[i][j]
= MAX(d[i][j], st1[front1][1] - AP * j);
}

front1
= rear1 = 0;
for (j = MaxP; j >= 0; j--) {
k
= j + BS <= MaxP ? (j + BS) : MaxP;
x
= d[i - W - 1][j] + BP * j;
while (front1 < rear1 && st1[rear1 - 1][1] <= x) rear1--;
st1[rear1][
0] = j;
st1[rear1
++][1] = x;
while (front1 < rear1 && st1[front1][0] > k) front1++;
d[i][j]
= MAX(d[i][j], st1[front1][1] - BP * j);
}

}
int mx = 0;
for (j = 0; j <= MaxP; j++) {
mx
= MAX(mx, d[T][j]);
}
printf(
"%d\n", mx);
}
return 0;
}

 

 

4.Subsequence [hdu-3530]

分析:跟前面的一些题不一样,此题已知区间最大、最小值的差的范围,求区间最大长度。依然用单调队列维护区间的最大、最小值,区间的右端是i,那怎样确定

区间的左端呢?最初,左端是1,随着右端的右移(即插入元素),区间的最大、最小值也发生着变化,那么就要判断变化以后是否满足题目条件,如果不满足,则左端点进行右移。

 

hdu-3530
#include <stdio.h>
#include
<string.h>
#include
<stdlib.h>
#include
<ctype.h>
#include
<math.h>
#include
<set>
#include
<map>
#include
<string>
#include
<queue>
#include
<vector>
#include
<algorithm>

using namespace std;

//#define SUPERBIN
#define NL1 100011
#define NL2 21
#define EP 1e-10
#define MAX(x,y) ((x)>(y)?(x):(y))
#define MIN(x,y) ((x)<(y)?(x):(y))
#define LL long long

int w[NL1], st[NL1], st1[NL1];
int front1, rear1, front, rear;
int n, m, k;

int main() {
#ifdef SUPERBIN
freopen(
"in.txt", "r", stdin);
freopen(
"out.txt", "w", stdout);
#endif
int T, t, r, cs = 1;
int i, j, a, b, c, x, y, z;
while (scanf("%d%d%d", &n, &m, &k) != EOF) {
for (i=1; i<=n; i++) scanf("%d", &w[i]);
front
= rear = front1 = rear1 = 0;
int left = 1;
x
= 0;
for (i=1; i<=n; i++) {
while (front<rear && w[st[rear-1]] <= w[i]) rear--;
st[rear
++] = i;
while (front1<rear1 && w[st1[rear1-1]]>= w[i]) rear1--;
st1[rear1
++] = i;
while (w[st[front]]-w[st1[front1]]>k) {
if (st[front]<st1[front1]) left = st[front++]+1; //left记录右移以后的左端点
else left = st1[front1++]+1;
}
if (w[st[front]]-w[st1[front1]]>=m) {
x
= MAX(x,i-left+1);
}
}
printf(
"%d\n", x);
}
return 0;
}

 

5.NOI-2005- 瑰丽的华尔兹

分析:d[k][i][j]表示在第k个时间段,划到位置(i,j)的最优值。在划动方向,和划动范围确定的情况下,d[k][i][j] = MAX(d[k-1][i1][j1] + dist),(i1,j1)为划动的起始位置,dist为划动的距离。因为划动是在一条直线上,可以用单调队列优化。

比如:当dir = 1 (方向为北),d[k][i][j] = MAX(d[k-1][i1][j]+i-i1) = MAX(d[k-1][i1][j]-i1) + i ,(i-i1<end-start+1).

 

瑰丽华尔兹
#include <stdio.h>
#include
<string.h>
#include
<stdlib.h>
#include
<ctype.h>
#include
<math.h>
#include
<set>
#include
<map>
#include
<string>
#include
<queue>
#include
<vector>
#include
<algorithm>

using namespace std;

//#define SUPERBIN
#define NL1 211
#define NL2 21
#define EP 1e-10
#define MAX(x,y) ((x)>(y)?(x):(y))
#define MIN(x,y) ((x)<(y)?(x):(y))
#define LL long long

char mp[NL1][NL1];
int d[NL1][NL1][NL1];
int st[NL1][2];
int n, m, x, y, k;

int main() {
#ifdef SUPERBIN
freopen(
"in.txt", "r", stdin);
freopen(
"out.txt", "w", stdout);
#endif
int T, r, cs = 1;
int i, j, j1, j2, a, b, c, s, t, dir;
while (scanf("%d%d%d%d%d", &n, &m, &x, &y, &k) != EOF) {
for (i = 1; i <= n; i++) scanf("%s", mp[i] + 1);
memset(d,
-1, sizeof (d));
d[
0][x][y] = 0;
for (i = 1; i <= k; i++) {
scanf(
"%d%d%d", &s, &t, &dir);
int front, rear;
switch (dir) {
case 1:
for (j1 = 1; j1 <= m; j1++) {
front
= rear = 0;
for (j2 = n; j2 >= 1; j2--) {
if (mp[j2][j1] == '.') {
if (d[i - 1][j2][j1] >= 0) {
while (front < rear && st[rear - 1][1] <= d[i - 1][j2][j1] + j2) rear--; //key: 别忘了加距离
st[rear][0] = j2;
st[rear
++][1] = d[i - 1][j2][j1] + j2;
}
while (front < rear && st[front][0] - j2 > t - s + 1) front++;
if (front < rear) d[i][j2][j1] = MAX(d[i][j2][j1], st[front][1] - j2);
}
else {
front
= rear = 0;
}
}
}
break;
case 2:
for (j1 = 1; j1 <= m; j1++) {
front
= rear = 0;
for (j2 = 1; j2 <= n; j2++) {
if (mp[j2][j1] == '.') {
if (d[i - 1][j2][j1] >= 0) {
while (front < rear && st[rear - 1][1] <= d[i - 1][j2][j1] - j2) {
rear
--;
}
st[rear][
0] = j2;
st[rear
++][1] = d[i - 1][j2][j1] - j2;
}
while (front < rear && j2 - st[front][0] > t - s + 1) front++;
if (front < rear) d[i][j2][j1] = MAX(d[i][j2][j1], st[front][1] + j2);
}
else {
front
= rear = 0;
}
}
}
break;
case 3:
for (j1 = 1; j1 <= n; j1++) {
front
= rear = 0;
for (j2 = m; j2 >= 1; j2--) {
if (mp[j1][j2] == '.') {
if (d[i - 1][j1][j2] >= 0) {
while (front < rear && st[rear - 1][1] <= d[i - 1][j1][j2] + j2) rear--;
st[rear][
0] = j2;
st[rear
++][1] = d[i - 1][j1][j2] + j2;
}
while (front < rear && st[front][0] - j2 > t - s + 1) front++;
if (front < rear) d[i][j1][j2] = MAX(d[i][j1][j2], st[front][1] - j2);
}
else {
front
= rear = 0;
}
}
}
break;
case 4:
for (j1 = 1; j1 <= n; j1++) {
front
= rear = 0;
for (j2 = 1; j2 <= m; j2++) {
if (mp[j1][j2] == '.') {
if (d[i - 1][j1][j2] >= 0) {
while (front < rear && st[rear - 1][1] <= d[i - 1][j1][j2] - j2) rear--;
st[rear][
0] = j2;
st[rear
++][1] = d[i - 1][j1][j2] - j2;
}
while (front < rear && j2 - st[front][0] > t - s + 1) front++;
if (front < rear) d[i][j1][j2] = MAX(d[i][j1][j2], st[front][1] + j2);
}
else {
front
= rear = 0;
}
}
}
break;
}
}
int ans = 0;
for (i = 1; i <= n; i++) {
for (j = 1; j <= m; j++) {
ans
= MAX(ans, d[k][i][j]);
}
}
printf(
"%d\n", ans);
}
return 0;
}

 

 

PS:相当不好想,实现也费了好大功夫,1更搞笑的是我居然把 stk[NL1][2]数组开成 stk[NL][0],直接导致了n莫名其妙的改变了,写了老长的输出才查出来,狂⊙﹏⊙b汗!

posted @ 2010-10-09 08:45  superbin  阅读(766)  评论(0编辑  收藏  举报