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;
}