Problem Description
蜘蛛牌是windows xp操作系统自带的一款纸牌游戏,游戏规则是这样的:只能将牌拖到比她大一的牌上面(A最小,K最大),如果拖动的牌上有按顺序排好的牌时,那么这些牌也跟着一起移动,游戏的目的是将所有的牌按同一花色从小到大排好,为了简单起见,我们的游戏只有同一花色的10张牌,从A到10,且随机的在一行上展开,编号从1到10,把第i号上的牌移到第j号牌上,移动距离为abs(i-j),现在你要做的是求出完成游戏的最小移动距离。
Input
第一个输入数据是T,表示数据的组数。
每组数据有一行,10个输入数据,数据的范围是[1,10],分别表示A到10,我们保证每组数据都是合法的。
Output
对应每组数据输出最小移动距离。
Sample Input
1
1 2 3 4 5 6 7 8 9 10
Sample Output
9
题意:找到那个最小代价的移动距离。比如这一题的样例中,1移到2上面,花费1个代价,然后12就视为一体了,然后再把12移到3上面,又花费1个代价,直到最后将所有的牌全部移到10下面,就结束,得到了最小移动代价为9。如果还是不明白题意,其实可以玩一玩电脑里的蜘蛛纸牌。
这个题要好好收藏,网上都说是一道很简单的dfs题,但就我个人而言,并不觉得它很简单。初次看到这道题的时候,我也知道可能是要用dfs来搜,但不知道如何搜。因为我刚开始就想错了方向,一直在纠结如何用dfs来更新数组,如何来表示一个牌移动到另一张牌下面的这个行为。我当时感觉不可能用一个好的方式来表达牌的移动,所以我就不知道从何下手了。
在网上看到大牛们的思路后,终于明白了!具体操作是这样的:每次,我们理论上都可以选择9种牌来移动(大小1–9,10不能移动),每一张牌的移动到的位置有几种选择呢?有些人说,那还用问吗,肯定是只有一种啊,只能移动到大小比它大一的牌下面啊。好,这个地方那就和我一样想错了。
比如我们现在要移大小为5的这张牌,按照我们上面说的,直接移到6的位置下就可以了,其实是不对的。因为6这张牌它可能已经被移动过,如果6这张牌在我移动5之前被移动过,那么6肯定是移动到了7的下面,那我们就得把5移到大小为7的牌下!那如果7也被移动过,那么意味着7, 6都在8的下面,那么我们又得把5移到8的下面了!
那么我们如何表示牌的移动呢?其实用一个标记数组就行了,因为每一张牌必定只能移动到大小比它大一的牌之下,所以只要tag[i] = 1就表示i这张牌被移动过!
文字说明可能有点啰嗦,可能看代码还好理解一些,确实感觉一些东西只可意会
#include<iostream>
#include<cmath>
#include<cstring>
using namespace std;
const int INF = 1000000;
const int maxn = 11;
int a[maxn]; //记录大小为s的牌所在的位置
int min_res = INF;
int tag[maxn];
void dfs(int k, int cost)
{
if(cost > min_res) //剪枝不能忘
return ;
if(k == 10)
{
if(cost < min_res)
min_res = cost;
return ;
}
for(int i = 1;i < 10;i++) //理论上,每次可以选择9种大小的牌移动(不考虑有牌移动过)
{
if(!tag[i]) //如果该大小的牌没有被移动
{
//那么它只能移动到比它大1的牌下面
//由于比它大1的牌可能已经移动到别的地方
for(int j = i + 1;j <= 10;j++)
{
if(!tag[j])
{
tag[i] = 1;
dfs(k + 1, cost + abs(a[i] - a[j]));
break; //这里好好理解,找到一个就跳出,不用往后再找,其实理由理解了很简单
}
}
tag[i] = 0; //这里回溯!
}
}
}
int main()
{
int t;
cin >> t;
while(t-- > 0)
{
memset(tag, 0, sizeof(0));
min_res = INF;
int s;
for(int i = 1;i <= 10;i++)
{
cin >> s; //输入牌的大小
a[s] = i; //表示大小为s的牌放在数组第i位
}
dfs(1, 0);
cout << min_res << endl;
}
return 0;
}
本题最大的收获就是让我体会到dfs居然可以这样用,在遍历的同时,利用数组下标和参数,就能将结果算出。在无形之中就完成了我认为的不可能解决的问题。
其实题中的移动并不需要我们真的把数组中的元素移来移去,只需要在脑海中移动