枚举子结构得到最优解的动态规划问题
当子问题的数量不多时,通常我们能够比较清晰地求出最优解的结构,然后理清各种状态之间转移的过程。但是,如果一个动态规划拥有多个子结构时,我们往往会觉得无从下手,面对这种情况,我们可以考虑下枚举子结构,然后得到动态规划的最优解。而且,有时候我们在枚举子结构时,还要运用另外一些最优结构。我们看看下面几个例子。
我们定义dp[i][j]表示从牌的大小为i到牌的大小为j这一串牌,通过移动得到满足条件的一堆牌的最小步数。对于牌1来说,他必须移到到2的上面,但是我们不知道,当他移到2位置上时2到底在哪,所以我们可以枚举2的位置。这样我们就得到了状态转移方程:dp[1][10] = dp[2][i] + dp[i][10] + dis[1][i] ; (2<=j<=10, dis[i][j]表示牌i和牌j之间的距离)。这样我们就用子问题的最优解构造出了原问题的最优解了。接下来我们可以利用子问题的最优解来递归定义问题的最优解。当然我们可以用递推来实现。这样问题便解决了。
递归实现:
#include <iostream>
#include <cmath>
using namespace std;
int num[11], dis[11][11];
void init() {
int i, j, a;
for( i=1; i<=10; ++i ) {
scanf( "%d", &a );
num[a] = i;
}
for( i=1; i<=10; ++i ) {
for( j=1; j<=10; ++j ) {
dis[i][j] = abs( num[i] - num[j] );
}
}
}
int solve(int l, int r ) {
int i, s = 99999;
if( l == r ) return 0;
if( r - l == 1 ) return dis[l][r];
for( i=l+1; i<=r; ++i ) {
int tp = solve( l+1, i ) + solve(i, r) + dis[l][i];
if( tp < s ) s = tp;
}
return s;
}
int main() {
// freopen( "c:/aaa.txt", "r", stdin );
int T;
while( scanf( "%d", &T ) == 1 ) {
while( T-- ) {
init();
printf( "%d\n", solve(1, 10));
}
}
return 0;
}
递推实现:
#include <iostream>
#include <cmath>
using namespace std;
char num[11], dis[11][11], dp[11][11];
void init() {
int i, j, a;
for( i=1; i<=10; ++i ) {
scanf( "%d", &a );
num[a] = i;
}
for( i=1; i<=10; ++i ) {
for( j=1; j<=10; ++j ) {
dp[i][j] = 0;
dis[i][j] = abs(num[i] - num[j] );
}
dp[i][i+1] = dis[i][i+1];
}
}
char solve() {
int i, j, k, len;
for( len=3; len<=10; ++len ) {
for( i=1; i<=11-len; ++i ) {
dp[i][i+len-1] = 999999;
for( j=i+1; j<=i+len-1; ++j ) {
if( dp[i+1][j] + dp[j][i+len-1] + dis[i][j] < dp[i][i+len-1]) dp[i][i+len-1] = dp[i+1][j] + dp[j][i+len-1] + dis[i][j];
}
}
}
return dp[1][10];
}
int main() {
// freopen( "c:/aaa.txt", "r", stdin );
char T;
while( scanf( "%d", &T ) == 1 ) {
while( T-- ) {
init();
printf( "%d\n", solve());
}
}
return 0;
}
横坐标上有n个点,然后选m个点,使得n个点中每个点离所选的点的最近距离的和的最小值。
当我们拿到题目,试着寻找问题最优解的子结构时,我们可以发现当我们放m个点的最后一点时,他有很多种情况。所以,接下来我们需要对所有的情况进行枚举。在放最后一个的时候,前m-1个已经放了,所以最后一个可能放在第m个位置到最后一个位置的所有位置。而这些状态会产生一个最优解。所以我们可以定义dp[i][j]为从1到i已经放了j个的最优解,则dp[n][m] = dp[i][m-1] + dis[i+1][n]; ( m-1<=i<=n-1,dis[i][j]表示点i到点j之间放一点,所得到的最小值)
#include <iostream>
#include <cmath>
using namespace std;
int n, m, num[205], dis[205][205], dp[202][35];
void input() {
for( int i=0; i<n; ++i ) scanf( "%d", &num[i] );
}
void getDis() {
int i, j, k, mid;
memset( dis, 0, sizeof(dis) );
for( i=0; i<n; ++i ) {
for( j=i+1; j<n; ++j ) {
mid = (i+j) / 2;
for( k=i; k<=j; ++k ) {
dis[i][j] += abs(num[k] - num[mid]);
}
}
}
}
void solve() {
int i, j, k;
memset( dp, 0, sizeof( dp ));
for( i=0; i<n; ++i ) dp[i][1] = dis[0][i];
for( i=2; i<=m; ++i ) {
for( j=i-1; j<n; ++j ) {
dp[j][i] = 9999999;
for( k=i-2; k<=j-1; ++k ) {
if( dp[k][i-1] + dis[k+1][j] < dp[j][i] ) dp[j][i] = dp[k][i-1] + dis[k+1][j];
}
}
}
printf( "Total distance sum = %d\n\n", dp[n-1][m]);
}
int main() {
// freopen( "c:/aaa.txt", "r", stdin );
int ca = 1;
while( scanf("%d %d", &n, &m), n+m ) {
printf( "Chain %d\n", ca++ );
input();
getDis();
solve();
}
return 0;
}
3.hdoj 1244 Max Sum Plus Plus Plus
#include <iostream>
using namespace std;
int n, m, num[1005], len[25], lensum[25], sum[1005];
int dp[1002][22];
void init() {
int i;
scanf( "%d", &m );
lensum[0] = 0;
for( i=1; i<=m; ++i ) {
scanf( "%d", &len[i] );
lensum[i] = lensum[i-1] + len[i];
}
sum[0] = 0;
for( i=1; i<=n; ++i ) {
scanf( "%d", &num[i] );
sum[i] = sum[i-1] + num[i];
}
}
void solve() {
int i, j, k, maxx;
memset( dp, 0, sizeof(dp) );
for( i=1; i<=n; ++i ) {
for( j=1; j<=m; ++j ) {
if( lensum[j-1] > i ) break;
if( i+len[j] > n ) continue;
maxx = 0;
for( k=lensum[j-1]; k<=i; ++k ) if( dp[k][j-1] > maxx ) maxx = dp[k][j-1];
dp[i+len[j]][j] = maxx + sum[i+len[j]] - sum[i];
}
}
maxx = 0;
for( i=1; i<=n; ++i ) if( dp[i][m] > maxx ) maxx = dp[i][m];
printf( "%d\n", maxx );
}
int main() {
// freopen("c:/aaa.txt", "r", stdin );
while( scanf("%d", &n) == 1 && n ) {
init();
solve();
}
return 0;
}
.
浙公网安备 33010602011771号