hdu 1584 蜘蛛牌

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值