Problem
acm.hdu.edu.cn/showproblem.php?pid=1584
题意
模仿 Windows 自带的蜘蛛纸牌,只有 1 ~ 10 这 10 张牌,小牌叠到大牌上。
一开始 10 张牌打横排成一行,从左到右分别编号 1 ~ 10。
将位置 i 上的牌叠到位置 j 上,路程为 abs ( i - j )。
问从小到大叠好这 10 张牌的最小总路程。
分析
思路一是 DFS。10 一定在原地不动,从 9 开始往前推。
对每一张牌 i,当前都有两种行为可选:马上叠到 i+1 所在位置;等后面若干张牌叠过来后,再一起叠到 i+1 所在位置。
很容易写出 dfs 函数如下:
void dfs(int tmp, int now) { if(now == 0) { cost = min(cost, tmp); return; } int p = pos[now]; tmp += abs(pos[now] - pos[now+1]); // 不改位置:先等若干牌叠过来,再一起过去 dfs(tmp, now-1); // 改位置:先仔己叠过去 pos[now] = pos[now+1]; dfs(tmp, now-1); // 复位 pos[now] = p; }
但这是错的。考虑的情况少了。
对于第 2 种策略,是等后面“若干张”叠过来,而上面的函数是等 now 前“所有”牌都叠过来之后,才一起过去。
举个例子,4 先不动,它可以:等 3 一起走;等 2、3 一起走;等1、2、3一起走。
那么,在考虑 1 的时候,它只能去到 2 所在位置,但 2 此时可能在:2 原来的位置(2先不动)、3 原来的位置(2先去到3的位置)、4 原来的位置(3载着2去到4,或2、3分别去到4)、…、10 原来的位置。
所以,另外用一个栈记录对于当前的 now来说,now+1有可能在哪些位置等它。在考虑 now 的时候,要考虑 now 去到栈里每一个位置的情况。
思路二是区间DP。
dp[ i ][ j ]:叠成以 i 牌开头、长度为 j 的一条顺子的最小路程
状态转移:将一条长顺子拆成两条短顺子,枚举断点。
dp[ i ][ j ] = min { dp[ i ][ k ] + dp[ i+k ][ j-k ] + abs( pos[ k ] - pos[ j ] ) | 1 <= k < j }(注:k 表示第 1 条短顺子的长度)
Source code
DFS版
#include <cstdio> #include <cstdlib> #include <stack> #include <algorithm> using namespace std; const int N = 10, BIG = 0x1f2f3f4f; int pos[N+1], cost; void dfs(int c, stack<int> s, int now) { if(!now) { cost = min(cost, c); return; } c += abs(pos[now] - pos[now+1]); if(c >= cost) // 剪枝 return; s.push(pos[now]); dfs(c, s, now-1); s.pop(); int p = pos[now]; for( ; !s.empty(); s.pop()) { pos[now] = s.top(); dfs(c, s, now-1); } pos[now] = p; } int main() { int t; scanf("%d", &t); while(t--) { for(int i=0, in; i<N; ++i) { scanf("%d", &in); pos[in] = i; } cost = BIG; stack<int> stk; stk.push(pos[N]); dfs(0, stk, N-1); printf("%d\n", cost); } return 0; }区间DP版
#include <cstdio> #include <cstring> #include <cstdlib> #include <algorithm> using namespace std; const int N = 10; int dp[N+1][N+1], pos[N+1]; int main() { int t; scanf("%d", &t); while(t--) { for(int i=0, in; i<N; ++i) { scanf("%d", &in); pos[in] = i; } memset(dp, 3, sizeof dp); for(int i=0; i<=N; ++i) dp[i][1] = 0; for(int w=1; w<=N; ++w) for(int i=1; i+w<=N+1; ++i) for(int j=1; j<w; ++j) dp[i][w] = min(dp[i][w], dp[i][j] + dp[i+j][w-j] + abs(pos[i+j-1]-pos[i+w-1])); printf("%d\n", dp[1][N]); } return 0; }