【练习07】 DFS 1011 蜘蛛牌

本文介绍了解决蜘蛛牌游戏中寻找将牌堆整理有序所需的最小移动步数问题的两种算法:深度优先搜索(DFS)与动态规划(DP)。DFS算法通过剪枝减少搜索空间,而DP算法则通过枚举子结构找到最优解,并使用记忆化技术加速计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

算法思路:

1. DFS,暴力搜索,需要剪枝;

2.1. dp(递归法),关键是递归函数的构造,如果想到了“状态转移方程”这一步是比较容易写出代码的;

2.2. dp(递推),关键是递推的初值和递推的顺序。当然首先需要想出状态转移方程。


下面分别列出这2种方法的详细分析:


方法一:125msAC,最慢的,思路很直接,看代码。

代码如下:

#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
/*简直就是一个悲剧  
自己写的错误百出 最后还是要学习别人的代码 才能AC
*/
const int INF=100000000;
int vis[12];
int a[12],ans;
void DFS(int cur,int temp)//cur代表已经移动了几张牌,temp代表目前移动耗费的步数,把步数写在这里解决了我一直的疑惑
{
    if(temp>=ans)return;  //剪枝 太重要了!!
    if(cur==9)     //原来写成10,只用移动9次 10是固定不变的 这里需要思考 我原先做的时候什么都没想 就一个劲儿的枚举
    {
        ans=temp; //原来temp=0,既然进入了这个语句 就已经确保当前值比原来的要优化
        return;   //回溯,可以自然解决改temp的问题
    }
     for(int i=1;i<10;i++)//递归里写上这句就是一个全排列了(+下面的判重)
     {
        if(!vis[i])
        {
            for(int k=i+1;k<=10;k++)//这个用来确定i牌要移到什么位置
            {
                if(!vis[k])//比如要移1了,如果2,3,4,5都已经被移动过了 那么这几张牌必定叠放在6的下面,所以要移到6的位置
                {
                    vis[i]=1;
                    DFS(cur+1,temp+abs(a[i]-a[k]));
                    break;//注意不要再这个地方回溯 如果回溯了 就像是又一个全排列 而且牌得移动不合理,比如2移到6了,结果回溯就直接跳过3~6到了7的下面
                }
            }
            vis[i]=0;//这里回溯
        }
     }
    return ;
}
int main()
{
    int cas,s;
    cin>>cas;
    while(cas--)
    {
        for(int i=1;i<=10;i++)
        {
             cin>>s;
             a[s]=i;//牌面为i的牌所在的位置
        }
        memset(vis,0,sizeof(vis));
         
        ans=INF;
        DFS(0,0);
        cout<<ans<<endl;
 
    }
}
转载自: http://www.cnblogs.com/sook/archive/2011/03/27/1996775.html


方法2:

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

1.hdoj  1584 蜘蛛牌

我们定义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之间的距离)。这样我们就用子问题的最优解构造出了原问题的最优解了。接下来我们可以利用子问题的最优解来递归定义问题的最优解。当然我们可以用递推来实现。这样问题便解决了。

转移方程:dp[l][r] = min{dp[l +1][j] + dp[j][r] + dis[l][j]}, j在区间[l + 1, r], j为整数。    其中dp的过程需要枚举所有可能的j。

递归实现, 15msAC, 代码如下:

//模板开始
#include <string>   
#include <vector>   
#include <algorithm>   
#include <iostream>   
#include <sstream>   
#include <fstream>   
#include <map>   
#include <set>   
#include <cstdio>   
#include <cmath>   
#include <cstdlib>   
#include <ctime>
#include<iomanip>
#include<string.h>
#define SZ(x) (int(x.size()))
using namespace std;

int toInt(string s){
	istringstream sin(s); 
	int t; 
	sin>>t; 
	return t;
}
template<class T> string toString(T x){
	ostringstream sout; 
	sout<<x; 
	return sout.str();
}
typedef long long int64;
int64 toInt64(string s){
	istringstream sin(s); 
	int64 t; 
	sin>>t;
	return t;
}
template<class T> T gcd(T a, T b){ 
	if(a<0) 
		return gcd(-a, b);
	if(b<0) 
		return gcd(a, -b);
	return (b == 0)? a : gcd(b, a % b);
}
#define ifs cin
//模板结束(通用部分)

#define INF 1<<30
int p[12];
int dis[12][12];
int dp[12][12];

void init()
{
	for(int i = 1; i <= 10; i++)
	{
		for(int j = 1; j <= 10; j++)
		{
			dp[i][j] = INF;
			dis[i][j] = abs(p[i] - p[j]);
		}
	}
}

int solve(int l, int r)
{
	int &t = dp[l][r];
	int s;
	if(l == r)
	{
		return 0; 
	}
	if(r - l == 1)
	{
		return dis[l][r];
	}
	if(t != INF)
	{
		return t;
	}
	for(int i = l + 1; i <= r; i++)
	{
		s = solve(l + 1, i) + dis[l][i] + solve(i, r);
		if(t > s)
		{
			t = s;
		}
	}
	return t;
}

//【练习07】 DFS 1011 蜘蛛牌
int main()
{
	//ifstream ifs("shuju.txt", ios::in);
	int T;
	int a;
	ifs>>T;
	for(int i = 0; i < T; i++)
	{	
		for(int j = 1; j <= 10; j++)
		{
			ifs>>a;
			p[a] = j;
		}
		init();
		int ans = solve(1, 10);
		cout<<ans<<endl;
	}

	return 0;
}



 递推实现,要深刻理解len,它表示dp[i][j]中j - i的最大长度,初始化的时候可以看做 len = 2,因为这时候还只可以确定dp[i][i + 1], 所以后面循环递推的时候需要让len从3开始逐步增长,直到10。


dp[i][i + 1] = dis[i][i + 1];
dp[i][i] = 0;

这两句初始化也很关键。

 15msAC,代码如下:

//模板开始
#include <string>   
#include <vector>   
#include <algorithm>   
#include <iostream>   
#include <sstream>   
#include <fstream>   
#include <map>   
#include <set>   
#include <cstdio>   
#include <cmath>   
#include <cstdlib>   
#include <ctime>
#include<iomanip>
#include<string.h>
#define SZ(x) (int(x.size()))
using namespace std;

int toInt(string s){
	istringstream sin(s); 
	int t; 
	sin>>t; 
	return t;
}
template<class T> string toString(T x){
	ostringstream sout; 
	sout<<x; 
	return sout.str();
}
typedef long long int64;
int64 toInt64(string s){
	istringstream sin(s); 
	int64 t; 
	sin>>t;
	return t;
}
template<class T> T gcd(T a, T b){ 
	if(a<0) 
		return gcd(-a, b);
	if(b<0) 
		return gcd(a, -b);
	return (b == 0)? a : gcd(b, a % b);
}
#define ifs cin
//模板结束(通用部分)

#define INF 1<<8
int p[12];
int dis[12][12];
int dp[12][12];

void init()
{
	for(int i = 1; i <= 10; i++)
	{
		for(int j = 1; j <= 10; j++)
		{
			dp[i][j] = INF;
			dis[i][j] = abs(p[i] - p[j]);
		}
		dp[i][i + 1] = dis[i][i + 1];
		dp[i][i] = 0;
	}
}

int solve()
{
	for(int len = 3; len <= 10; len++)
	{
		for(int i = 1; i + len - 1 <= 10; i++)
		{
			for(int 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];
}

//【练习07】 DFS 1011 蜘蛛牌
int main()
{
	//ifstream ifs("shuju.txt", ios::in);
	int T;
	int a;
	ifs>>T;
	for(int i = 0; i < T; i++)
	{	
		for(int j = 1; j <= 10; j++)
		{
			ifs>>a;
			p[a] = j;
		}
		init();
		int ans = solve();
		cout<<ans<<endl;
	}

	return 0;
}



转载自: http://www.cnblogs.com/crazyac/articles/1996475.html

自己看过上面的递归版dp,加了记忆化,改进了一下,0msAC,代码如下:

#include <iostream>
#include <cmath>
using namespace std;

#define INF 1<<30
int num[11], dis[11][11];
int 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 ) {
			dis[i][j] = abs( num[i] - num[j] );
			dp[i][j] = INF;
		}
	}
}



int solve(int l, int r ) {
	int i, s;
	if( l == r ) return 0;
	if( r - l == 1 ) return dis[l][r];
	if(dp[l][r] != INF)
	{
		return dp[l][r];
	}
	for( i=l+1; i<=r; ++i ) {
		s = solve( l+1, i ) + solve(i, r) + dis[l][i];
		if( dp[l][r] > s ) dp[l][r] = s;
	}
	return dp[l][r];
}




int main() {
	//freopen( "c:/aaa.txt", "r", stdin );
	int T;
	scanf( "%d", &T );
	while( T-- ) {
		init();
		printf( "%d\n", solve(1, 10));
	}

	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值