枚举子结构得到最优解的动态规划问题
当子问题的数量不多时,通常我们能够比较清晰地求出最优解的结构,然后理清各种状态之间转移的过程。但是,如果一个动态规划拥有多个子结构时,我们往往会觉得无从下手,面对这种情况,我们可以考虑下枚举子结构,然后得到动态规划的最优解。而且,有时候我们在枚举子结构时,还要运用另外一些最优结构。我们看看下面几个例子。
我们定义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; }
.