递归算法(二)


国际象棋

众所周知,“八皇后” 问题是求解在国际象棋棋盘上摆放 $8$ 个皇后,使得两两之间互不攻击的方案数。

已经学习了很多算法的小蓝觉得 “八皇后” 问题太简单了,意犹未尽。作为一个国际象棋迷,他想研究在 $N \times M$ 的棋盘上,摆放 $K$ 个马,使得两两之间互不攻击有多少种摆放方案。

由于方案数可能很大,只需计算答案除以 $1000000007$ (即 $10^9 + 7$) 的余数。

如下图所示,国际象棋中的马摆放在棋盘的方格内,走 “日” 字,位于 $(x, y)$ 格的马(第 $x$ 行第 $y$ 列)可以攻击 $(x + 1, y + 2)$、$(x + 1, y − 2)$、$(x − 1, y + 2)$、$(x − 1, y − 2)$、$(x + 2, y + 1)$、$(x + 2, y − 1)$、$(x − 2, y + 1)$ 和 $(x − 2, y − 1)$ 共 $8$ 个格子。

输入格式

输入一行包含三个正整数 $N, M, K$,分别表示棋盘的行数、列数和马的个数。

输出格式

输出一个整数,表示摆放的方案数除以 $1000000007$ (即 $10^9 + 7$) 的余数。

数据范围

对于 $5%$ 的评测用例,$K = 1$;
对于另外 $10%$ 的评测用例,$K = 2$;
对于另外 $10%$ 的评测用例,$N = 1$;
对于另外 $20%$ 的评测用例,$N, M ≤ 6,K ≤ 5$;
对于另外 $25%$ 的评测用例,$N ≤ 3,M ≤ 20,K ≤ 12$;
对于所有评测用例,$1 ≤ N ≤ 6,1 ≤ M ≤ 100,1 ≤ K ≤ 20$。

输入样例1:

1 2 1

输出样例1:

2

输入样例2:

4 4 3

输出样例2:

276

输入样例3:

3 20 12

输出样例3:

914051446

题解:

这道题正解用状态压缩 $dp$(深搜求方案数的基本上可以用 $dp$ 来更快的解决,毕竟空间换时间嘛),这里使用暴力深搜,虽然只能过一半,但是可以很好的训练深搜回溯算法。

深搜树就是八个分支(对应八个方向)。dfs函数有三个参数,横纵坐标,当前放了多少马。

对于递归出口有两个,一个是我走到最后一行了,不能往下走路,但还没有放到 $k$ 个马的数量;第二个就是我放到了 $k$ 个马的数量。当然还要回溯将放马的地方还原出来(用个bool数组就可以了)。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;

const int N = 110,mod=1e9+7;
int cnt[N][N];
int res=0;
int dx[8] = {-2, -1, 1, 2, 2, 1, -1, -2};
int dy[8] = {1, 2, 2, 1, -1, -2, -2, -1};
int n,m,k;
void dfs(int x,int y,int sum)				//分别枚举棋盘的每个位置放马或不放马。
{ 
    if(sum==k)
    {
        res=(res+1)%mod;
        return ;
    }
    if(y>m)
    {
        y=1,x++;
        if(x>n) return ;
    }

    dfs(x,y+1,sum);							//(x,y)不放马

    if(!cnt[x][y])							//如果(x,y)可以放马,就放马
    {
    	//更新一下状态(在(x,y)处放马后,哪些位置不能放马了)
        cnt[x][y]++;
        for(int i=0;i<8;i++)
        {
            int a=x+dx[i],b=y+dy[i];
            if(a<1||a>n||b<1||b>m) continue;
            cnt[a][b]++;
        }

        dfs(x,y+1,sum+1);					//放马后,马的数量sum要 +1
    	//恢复现场
        cnt[x][y]--;
        for(int i=0;i<8;i++)
        {
            int a=x+dx[i],b=y+dy[i];
            if(a<1||a>n||b<1||b>m) continue;
            cnt[a][b]--;
        }
    }

}
int main()
{
    cin>>n>>m>>k;
    dfs(1,1,0);							//从(1,1)这个位置开始放马,最开始总共放了0个马
    printf("%d",res);
    return 0;
}

地宫取宝

X 国王有一个地宫宝库,是 $n \times m$ 个格子的矩阵,每个格子放一件宝贝,每个宝贝贴着价值标签。

地宫的入口在左上角,出口在右下角。

小明被带到地宫的入口,国王要求他只能向右或向下行走。

走过某个格子时,如果那个格子中的宝贝价值比小明手中任意宝贝价值都大,小明就可以拿起它(当然,也可以不拿)。

当小明走到出口时,如果他手中的宝贝恰好是 $k$ 件,则这些宝贝就可以送给小明。

请你帮小明算一算,在给定的局面下,他有多少种不同的行动方案能获得这 $k$ 件宝贝。

输入格式

第一行 $3$ 个整数,$n,m,k$,含义见题目描述。

接下来 $n$ 行,每行有 $m$ 个整数 $C_i$ 用来描述宝库矩阵每个格子的宝贝价值。

输出格式

输出一个整数,表示正好取 $k$ 个宝贝的行动方案数。

该数字可能很大,输出它对 $1000000007$ 取模的结果。

数据范围

$1 \le n,m \le 50$,
$1 \le k \le 12$,
$0 \le C_i \le 12$

输入样例1:

2 2 2
1 2
2 1

输出样例1:

2

输入样例2:

2 3 2
1 2 3
2 1 5

输出样例2:

14

题解:

还是一样,递归求方案数,还是可以用dp解决。但是这里数据小,dfs也可以AC。

每步的数据是坐标位置、手中物品数量、当前物品价值的最大值,因此dfs函数有四个参数

#include<iostream>
using namespace std;
const int N = 55;
int g[N][N];
int n,m,k;
int res;
int MOD = 1000000007;

int dfs(int x,int y,int cnt,int value)   //x,y坐标,手里物品数,手里最大价值
{
	if(x>n||y>m||cnt>k) return 0;  //越界
	if(x==n&&y==m)						//到达终点
	{
        if(cnt==k||(cnt==k-1&&g[x][y]>value))  		//若在最后手中数量等于k,
            return 1;                       		//或者最后手中数量等于k-1且最后的物品价值大于前面所有的物品价值的最大值                        
        else                               			//(意思可以拿最后一件,拿了正好手中物品数量等于k)
            return 0;
	}	
	if(g[x][y] > value)
	{
		return dfs(x+1,y,cnt+1,g[x][y]) + dfs(x,y+1,cnt+1,g[x][y]) + dfs(x,y+1,cnt,value) + dfs(x+1,y,cnt,value);
        //取,去下一步,取,去右一步,不取,去右一步,取,去下一步
	}
	else return dfs(x,y+1,cnt,value) + dfs(x+1,y,cnt,value);
}

int main()
{
	cin>>n>>m>>k;
	for(int i = 1;i<=n;i++)
	{
		for(int j = 1;j<=m;j++) cin>>g[i][j];
	}
	 dfs(1,1,0,-1);   							//表示从(1,1)开始,手中没有物品且价值为-1。
	 res = dfs(1,1,0,-1)%MOD;
	 cout<<res;
}

文章作者: 小小星仔
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 小小星仔 !
评论
  目录