动态规划[专题总结][持续更新...]

论文推荐一:张辰[2000国家集训队]

 

 

摘要
   
     
动态规划是信息学竞赛中的常见算法,本文的主要内容就是分析它的特点。
文章的第一部分首先探究了动态规划的本质,因为动态规划的特点是由它的本质所决定的。
第二部分从动态规划的设计和实现这两个角度分析了动态规划的多样性、模式性、技巧性这三个特点。
第三部分将动态规划和递推、搜索、网络流这三个相关算法作了比较,从中探寻动态规划的一些更深层次的特点。
文章在分析动态规划的特点的同时,还根据这些特点分析了我们在解题中应该怎样利用这些特点,怎样运用动态规划。这对我们的解题实践有一定的指导意义。

 

 

[线性模型]

1.《编辑距离》问题

问题模型:给定两个文本串X和Y,和一个操作集:{s1,s2...,sp}(比如删除一个字符等),每种操作对应一定的花费。求将文本串X变成Y的最小花费。

定义状态:d[i,j]为将串X前i个字符变成Y前j个字符的最小花费,转移方程

d[i,j] = MIN(d[i+dtk1][j+dtk2] + cost[k]), (其中dtk1和dtk2为执行操作k后串的变化)。

参考blog:Simon

相关题目:NKU-1131、PKU-3280、PKU-3356

 

nku-1131
   
     
//
#include < stdio.h >
#include
< string .h >
#define MIN(x,y) ((x)<(y)?(x):(y))
#define NL 2002

int d[NL], d1[NL];

int main()
{
char a[NL], b[NL];
int m, n;
int i, j;
while (scanf( " %s%s " , a + 1 , b + 1 ) != EOF) {
m
= strlen(a + 1 );
n
= strlen(b + 1 );
memset(d,
0 , sizeof (d));
for (i = 0 ; i <= m; i ++ ) d1[i] = i; // key 1
for (i = 1 ; i <= n; i ++ ) {
d[
0 ] = i; // key 1
for (j = 1 ; j <= m; j ++ ) {
if (b[i] == a[j]) d[j] = d1[j - 1 ];
else d[j] = MIN(d1[j], MIN(d[j - 1 ], d1[j - 1 ])) + 1 ;
}
for (j = 0 ; j <= m; j ++ )
d1[j]
= d[j];
}
printf(
" %d\n " , d[m]);
}
return 0 ;
}

 

 

 

pku-3280:

思路:给定串S,定义状态d[i,j]为将串Sn...j变成S1...i的最小花费,转移方程为:

d[i,j] = MIN(d[i-1,j] + cost[i], d[i,j+1] + cost[j]) ,(i<=i<n, i<j)

 

pku-3280
   
     
// 保留insert和delete中cost小的。
#include < stdio.h >
#include
< string .h >
#define MIN(x,y) ((x)<(y)?(x):(y))
#define NL 2002

int d[NL][NL];

int main()
{
char a[NL], b[ 4 ];
int m, n;
int cost[ 27 ], c1, c2;
int i, j;
// freopen("in.txt", "r", stdin);
while (scanf( " %d%d%s " , & m, & n, a + 1 ) != EOF) {
for (i = 0 ; i < m; i ++ ) {
scanf(
" %s%d%d " , b, & c1, & c2);
cost[b[
0 ] - ' a ' ] = MIN(c1,c2);
}
memset(d,
0 , sizeof (d));
int _min = 0x7fffffff ;
for (i = n; i >= 1 ; i -- ) {
d[
0 ][i] = d[ 0 ][i + 1 ] + cost[a[i] - ' a ' ]; // key 1
}
if (n > 1 ) _min = MIN(d[ 0 ][ 1 ], d[ 0 ][ 2 ]);
else _min = d[ 0 ][ 1 ];
for (i = 1 ; i <= n; i ++ ) {
d[i][n
+ 1 ] = d[i - 1 ][n + 1 ] + cost[a[i] - ' a ' ]; // key 1
for (j = n; j > i; j -- ) {
if (a[i] == a[j]) d[i][j] = d[i - 1 ][j + 1 ];
else d[i][j] = MIN(d[i - 1 ][j] + cost[a[i] - ' a ' ], d[i][j + 1 ] + cost[a[j] - ' a ' ]);
}
_min
= MIN(_min, MIN(d[i][i + 1 ], d[i - 1 ][i + 1 ]));
}
printf(
" %d\n " , _min);
}
return 0 ;
}

 

 

[二维DP]

1.pku-1191

描述:经典的《棋盘分割》问题。<参考《黑书》>

思路:先化简公式,dat^2  = 1/n(∑[xi^2 - 2*xi*xv + xv^2]) = 1/n(∑[xi^2]) - (xv^2). 关键是求分割n次得到的矩形总分的平均和的最小值。

考虑左上角坐标为(x1,y1),右下角坐标为(x2,y2)的棋盘,设它的总分和为s[x1,y1,x2,y2],切割k次得到的k+1块矩形的总分平方和最小值为d[k,x1,y1,x2,y2]。

切割可以横着切,也可纵着切,于是状态转移方程为:

d[k,x1,y1,x2,y2] = min {

min{ d[k-1,x1,y1,a,y2] + s[a+1,y1,x2,y2], d[k-1,a+1,y1,x2,y2] + s[x1,y1,a,y2] } (x1 <= a < x2),//纵着切

min{d[k-1,x1,y1,x2,b] + s[x1,b+1,x2,y2], d[k-1,x1,b+1,x2,y2] + s[x1,y1,x2,b]} (y1 <= b < y2),//横着切

}

棋盘边长m,状态数:m^4*n,决策数:O(m).

 

2.hdu-3127

描述:裁剪衣服。

思路:给定切割的方式(即,衣服样式),定义状态d[x,y,k]表示大小为x*y的布用前k个样式切得最大price。对于一种样式要切出来需要两下,切的方式有两种:横+纵。

d[x,y,k] = max{

d[x-a[k],y,k] + d[a[k],y-b[k],k],

d[x,y-b[k],k] + d[x-a[k],b[k],k]

}+c[k]

对于每种样式(x1,y1),反过来(y1,x1)也是一种样式。

 

3.Pku-2948

描述:见题。[街道问题变形]

思路:状态d[i,j],表示大小为i*j的油田能得到的最优值,决策为:对于位置(i,j),要么East->West,要么South->Noth,得转移方程

d[i,j] = max {

d[i-1,j]+a[i][j], //E->W, a[i][j] = ∑(a[i][k])(1<=k<=j)

d[i,j-1]+b[i][j], //S->N, b[i][j] = ∑(b[k][j])(1<=k<=i)

}

 

4.Pku-3034

描述:打鼹鼠。

思路:按时间来划分阶段,阶段变量t,状态d[x,y,t]表示,在时刻t,锤子放在位置(x,y),打到鼹鼠的最大值。

d[x,y,t] = max{d[x1,y1,t-1] + c[x,y,x1,y1] | (x1,y1)->(x,y)为所有可能得在t时刻的锤子移动路径,c[]表示路径上的鼹鼠数}

key1:锤子可以放在坐标系外;

key2:状态量O(n3), 决策量O(n2)。

pku-3034
   
     
// Time:1094MS
#include < stdio.h >
#include
< string .h >
#define MAX(x,y) ((x)>(y)?(x):(y))

int n, d, m;
int dp[ 31 ][ 31 ][ 11 ];
bool ext[ 31 ][ 31 ][ 11 ];
bool dis[ 31 ][ 31 ][ 31 ][ 31 ];
int gd[ 31 ][ 31 ][ 31 ][ 31 ];

int ABS( int a) {
if (a > 0 ) return a;
return - a;
}

int gcd( int a, int b) {
if (b == 0 ) return a;
return gcd(b, a % b);
}

int main() {
int i, j, k;
int mt, x, y, t;
// freopen("whacamole1.in", "r", stdin);
// freopen("in.txt", "r", stdin);
while (scanf( " %d%d%d " , & n, & d, & m) != EOF) {
if ( ! n && ! d && ! m) break ;
memset(ext,
0 , sizeof (ext));
mt
= 0 ;
for (i = 0 ; i < m; i ++ ) {
scanf(
" %d%d%d " , & x, & y, & t);
ext[x
+ d][y + d][t] = 1 ;
if (t > mt) mt = t;
}

memset(dp,
0 , sizeof (dp));
int i_t, i1, j1;
n
+= 2 * d;
for (i = 0 ; i < n; i ++ ) {
for (j = 0 ; j < n; j ++ ) {
for (i1 = 0 ; i1 < n; i1 ++ ) {
for (j1 = 0 ; j1 < n; j1 ++ ) {
if ((i - i1) * (i - i1) + (j - j1) * (j - j1) <= d * d)
dis[i][j][i1][j1]
= 1 ;
else
dis[i][j][i1][j1]
= 0 ;
if (i != i1 && j != j1) {
gd[i][j][i1][j1]
= gcd(ABS(i - i1), ABS(j - j1));
}
}
}
}
}
int dt, dt1;
for (i_t = 1 ; i_t <= mt; i_t ++ ) {
for (i = 0 ; i < n; i ++ ) {
for (j = 0 ; j < n; j ++ ) {
for (i1 = 0 ; i1 < n; i1 ++ ) {
for (j1 = 0 ; j1 < n; j1 ++ ) {
if (dis[i][j][i1][j1]) {
if (i == i1 && j == j1) {
if (ext[i][j][i_t])
dp[i][j][i_t]
= MAX(dp[i][j][i_t], dp[i1][j1][i_t - 1 ] + 1 );
else
dp[i][j][i_t]
= MAX(dp[i][j][i_t], dp[i1][j1][i_t - 1 ]);
}
else if (i == i1) {
if (j > j1) dt = 1 ;
else dt = - 1 ;
t
= 0 ;
for (k = j1; k != j; k += dt) {
if (ext[i][k][i_t]) t ++ ;
}
if (ext[i][k][i_t]) t ++ ;
dp[i][j][i_t]
= MAX(dp[i][j][i_t], dp[i1][j1][i_t - 1 ] + t);
}
else if (j == j1) {
if (i > i1) dt = 1 ;
else dt = - 1 ;
t
= 0 ;
for (k = i1; k != i; k += dt) {
if (ext[k][j][i_t]) t ++ ;
}
if (ext[k][j][i_t]) t ++ ;
dp[i][j][i_t]
= MAX(dp[i][j][i_t], dp[i1][j1][i_t - 1 ] + t);
}
else {
k
= gd[i][j][i1][j1];
dt
= (i - i1) / k;
dt1
= (j - j1) / k;
t
= 0 ;
for (x = i1, y = j1; x != i && y != j; x += dt, y += dt1) {
if (ext[x][y][i_t]) t ++ ;
}
if (ext[x][y][i_t]) t ++ ;
dp[i][j][i_t]
= MAX(dp[i][j][i_t], dp[i1][j1][i_t - 1 ] + t);
}
}
}
}
}
}
}
int ans = 0 ;
for (i = 0 ; i < n; i ++ ) {
for (j = 0 ; j < n; j ++ )
ans
= MAX(ans, dp[i][j][mt]);
}
printf(
" %d\n " , ans);
}
return 0 ;
}

 

你可能感兴趣的:(动态规划)