DFS训练集

1、数独问题
你一定听说过“数独”游戏。
如【图1.png】,玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个同色九宫内的数字均含1-9,不重复。
在这里插入图片描述
数独的答案都是唯一的,所以,多个解也称为无解。

本图的数字据说是芬兰数学家花了3个月的时间设计出来的较难的题目。但对会使用计算机编程的你来说,恐怕易如反掌了。

本题的要求就是输入数独题目,程序输出数独的唯一解。我们保证所有已知数据的格式都是合法的,并且题目有唯一的解。

格式要求:
输入9行,每行9个数字,0代表未知,其它数字为已知。
输出9行,每行9个数字表示数独的解。

例如:
输入(即图中题目):
005300000
800000020
070010500
400005300
010070006
003200080
060500009
004000030
000009700

程序应该输出:
145327698
839654127
672918543
496185372
218473956
753296481
367542819
984761235
521839764

再例如,输入:
800000000
003600000
070090200
050007000
000045700
000100030
001000068
008500010
090000400

程序应该输出:
812753649
943682175
675491283
154237896
369845721
287169534
521974368
438526917
796318452

资源约定:
峰值内存消耗 < 256M
CPU消耗 < 2000ms

【分析】:回溯法每个位置进行试探。判断能否装入的条件:在同行同列中不能出现相同的数字,并且在一个边长为3的正方形中,不能有重复的数字出现。

/**用数组来标记每行每列已经出现的数字**/
rvis[x][value]   = 1; //表示第x行数字为value已经存在
rvis[y][value]   = 1; //表示第y列数字为value已经存在

//对于正方形区域
int check(int x,int y,int value)
{
	int m=x/3*3,n=y/3*3;
	for(int i=m; i<m+3; i++)
		for(int j=n; j<n+3; j++)
			if(a[i][j]==value)
				return 0;
	return 1;
}

完整的代码:

//数独
#include<iostream>
#include<string.h>
using namespace std;
int a[10][10];
int rvis[15][15];
int cvis[15][15];

int check(int x,int y,int value)//检查方形中value是否出现
{
	int m=x/3*3,n=y/3*3;
	for(int i=m; i<m+3; i++)
		for(int j=n; j<n+3; j++)
			if(a[i][j]==value)
				return 0;
	return 1;
}

void dfs(int x,int y)
{
	if(x > 8)
	{
		for(int i=0; i<9; i++)
		{
			for(int j=0; j<9; j++)
				cout<<a[i][j];
			cout<<endl;
		}
		return;
	}
	
	if(a[x][y] == 0)
	{
		for(int i=1; i<10; i++)
		{
			if(!rvis[x][i] && !cvis[y][i] && check(x,y,i))//可以放入 
			{
				rvis[x][i] = 1;
				cvis[y][i] = 1;
				a[x][y] = i;
				if(y==8)
					dfs(x+1,0);
				else
					dfs(x,y+1);
				a[x][y]=0;//回溯
				rvis[x][i] = 0;
				cvis[y][i] = 0;
			}
		}
	}
	else
	{
		if(y==8)
			dfs(x+1,0);
		else
			dfs(x,y+1);
	}
}

int main()
{
	memset(rvis,0,sizeof(rvis));//赋值
	memset(cvis,0,sizeof(cvis));
	
	char str[9][9];//注意输入的是字符串
	for(int i=0; i<9; i++)
		gets(str[i]);
	
	for(int i=0; i<9; i++)
	{
		for(int j=0; j<9; j++)
		{
			a[i][j] = str[i][j]-'0';//将输入的字符串转换成数字
			if(a[i][j] != 0)
			{
				rvis[i][a[i][j]] = 1;
				cvis[j][a[i][j]] = 1;
			}		
		}
	}
	
	dfs(0,0);//进行深度优先搜索
	return 0;
} 

2、7对数字
今有7对数字:两个1,两个2,两个3,…两个7,把它们排成一行。
要求,两个1间有1个其它数字,两个2间有2个其它数字,以此类推,两个7之间有7个其它数字。如下就是一个符合要求的排列:

17126425374635

当然,如果把它倒过来,也是符合要求的。

请你找出另一种符合要求的排列法,并且这个排列法是以74开头的。

注意:只填写这个14位的整数,不能填写任何多余的内容,比如说明注释等。

【分析】:直接DFS

#include<iostream>
#include<string.h>
using namespace std;
int a[15];
int vis[15];

void dfs(int n)
{
	if(n==4 || n==7)
	{
		dfs(n+1); 
		return;
	}
	
	if(n>7)
	{
		for(int i=0;i<14; i++)
			cout<<a[i];
		cout<<endl;
		return;
	}
	
	for(int i=2; i<13-n; i++)
	{
		if(!vis[i] && !vis[i+n+1])
		{
			a[i]=a[i+n+1]=n;
			vis[i]=vis[i+n+1]=1;
			dfs(n+1);
			a[i]=a[i+n+1]=0;
			vis[i]=vis[i+n+1]=0;
		}
	}
}

int main()
{
	memset(vis,0,sizeof(vis));
	vis[0]=vis[8]=1;
	vis[1]=vis[6]=1;
	a[0]=a[0+7+1]=7;
	a[1]=a[1+4+1]=4;
	dfs(1);
	return 0;
}

3、九数组分数
1,2,3…9 这九个数字组成一个分数,其值恰好为1/3,如何组法?

下面的程序实现了该功能,请填写划线部分缺失的代码。

#include <stdio.h>

void test(int x[])
{
int a = x[0]*1000 + x[1]*100 + x[2]*10 + x[3];
int b = x[4]*10000 + x[5]*1000 + x[6]*100 + x[7]*10 + x[8];

if(a*3==b) printf("%d / %d\n", a, b);

}

void f(int x[], int k)
{
int i,t;
if(k>=9){
test(x);
return;
}

for(i=k; i<9; i++){
	{t=x[k]; x[k]=x[i]; x[i]=t;}
	f(x,k+1);
	_____________________________________________ // 填空处
}

}

int main()
{
int x[] = {1,2,3,4,5,6,7,8,9};
f(x,0);
return 0;
}

//DFS的回溯
{t=x[k]; x[k]=x[i]; x[i]=t;}

4、牌型种数

小明被劫持到X赌城,被迫与其他3人玩牌。
一副扑克牌(去掉大小王牌,共52张),均匀发给4个人,每个人13张。
这时,小明脑子里突然冒出一个问题:
如果不考虑花色,只考虑点数,也不考虑自己得到的牌的先后顺序,自己手里能拿到的初始牌型组合一共有多少种呢?

#include<iostream>
using namespace std;
int ans=0;

void dfs(int n,int sum)
{
	if(n > 13)
	{
		if(sum == 13)
			ans++;
		return;
	}
	if(sum > 13)
		return;
	
	for(int i=0; i<5; i++)//对于 n 个点数的牌,拿几张 
	{
		sum += i;
		dfs(n+1,sum);
		sum -= i;
	}
}

int main()
{
	dfs(1,0);
	cout<<ans<<endl;
	return 0;
}

5、垒骰子

赌圣atm晚年迷恋上了垒骰子,就是把骰子一个垒在另一个上边,不能歪歪扭扭,要垒成方柱体。
经过长期观察,atm 发现了稳定骰子的奥秘:有些数字的面贴着会互相排斥!
我们先来规范一下骰子:1 的对面是 4,2 的对面是 5,3 的对面是 6。
假设有 m 组互斥现象,每组中的那两个数字的面紧贴在一起,骰子就不能稳定的垒起来。
atm想计算一下有多少种不同的可能的垒骰子方式。
两种垒骰子方式相同,当且仅当这两种方式中对应高度的骰子的对应数字的朝向都相同。
由于方案数可能过多,请输出模 10^9 + 7 的结果。

不要小看了 atm 的骰子数量哦~

「输入格式」
第一行两个整数 n m
n表示骰子数目
接下来 m 行,每行两个整数 a b ,表示 a 和 b 数字不能紧贴在一起。

「输出格式」
一行一个数,表示答案模 10^9 + 7 的结果。

「样例输入」
2 1
1 2

「样例输出」
544

「数据范围」
对于 30% 的数据:n <= 5
对于 60% 的数据:n <= 100
对于 100% 的数据:0 < n <= 10^9, m <= 36

资源约定:
峰值内存消耗 < 256M
CPU消耗 < 2000ms

#include<iostream>
using namespace std;
#define mod 1000000007
int map[]={0,4,5,6,1,2,3};//存放每个面的对应面
int rule[7]={0};//存放不能相对的面
int m,n;
long long ans=0;

void dfs(int height,int up) 
{
	if(height > n)
	{
		ans=(ans+1)%mod;
		return;
	}
	
	for(int i=1; i<=6; i++)
	{
		if(rule[up] != i)
			dfs(height+1,map[i]);
	}
	
}

int main()
{
	cin>>n>>m;

	for(int i=0; i<m; i++)
	{
		int a,b;
		cin>>a>>b;
		rule[a]=b;
		rule[b]=a;
	}
	dfs(1,0);
	for(int i=0; i<n; i++)//对于每一层的骰子,都可以进行旋转,四个方向
		ans = ans%mod*4;
	cout<<ans<<endl;
	return 0;
} 

6、方格填数

在2行5列的格子中填入1到10的数字。
要求:
相邻的格子中的数,右边的大于左边的,下边的大于上边的。

如【图1.png】所示的2种,就是合格的填法。

请你计算一共有多少种可能的方案。

在这里插入图片描述

#include<iostream>
using namespace std;
int map[2][5];
int vis[10]={0};
int ans=0;

void dfs(int x,int y)
{
	if(x==1 && y==4)
	{
		ans++;
		return;
	}
	
	for(int i=2; i<=9; i++) 
	{
		if(vis[i]) continue;
		if(x==0)
		{
			if(map[x][y-1] < i)//在第一行时,只比较右边比左边大
			{
				map[x][y]=i;
				vis[i]=1;
				if(y==4)
					dfs(x+1,0);
				else
					dfs(x,y+1);
				vis[i]=0;
			}
		}
		else
		{
			if(map[x-1][y] < i)//比较下边比上边大
			{
				if(y>0 && map[x][y-1] > i)//比较右边比左边大
					continue;
											
				map[x][y]=i;
				vis[i]=1;
				if(y==4)
					dfs(x+1,0);
				else
					dfs(x,y+1);
				vis[i]=0;
			}
		}
	}
} 

int main()
{
	map[0][0]=1;
	map[1][4]=10;
	
	dfs(0,1);
	cout<<ans<<endl;
	return 0;
}

7、四阶幻方

把1~16的数字填入4x4的方格中,使得行、列以及两个对角线的和都相等,满足这样的特征时称为:四阶幻方。

四阶幻方可能有很多方案。如果固定左上角为1,请计算一共有多少种方案。
比如:
1 2 15 16
12 14 3 5
13 7 10 4
8 11 6 9

以及:
1 12 13 8
2 14 7 11
15 3 10 6
16 5 4 9

就可以算为两种不同的方案。

#include<iostream>
#include<string.h>
using namespace std;
int map[4][4];
int vis[17]={0};
int ans=0;

int check()
{
	for(int i=0; i<4; i++)
	{
		int sum=0;
		for(int j=0; j<4; j++)
		{
			 sum+=map[j][i];
		}
		if(sum != 34)
			return 0;
	}
	int a=map[0][0]+map[1][1]+map[2][2]+map[3][3];
	int b=map[0][3]+map[1][2]+map[2][1]+map[3][0];
	if(a!=34 || b!=34)
		return 0;
	return 1;		
}

void dfs(int x,int y,int sum)
{
	if(x>3)
	{
		if(check())
			ans++;
		return;
	}
	
	for(int i=2; i<=16; i++)
	{
		if(!vis[i])
		{
			if(sum+i > 34)//剪枝
				break;
			map[x][y]=i;
			vis[i]=1;
			if(y==3){
				if(sum+i == 34)//判断一行总和值
					dfs(x+1,0,0);
			}
			else
				dfs(x,y+1,sum+i);
			vis[i]=0;
		}
	}
}

int main()
{
	memset(map,0,sizeof(map));
	map[0][0]=1;
	vis[1]=1;
	dfs(0,1,1);
	cout<<ans<<endl;
	return 0;
} 

8、方格填数
如下的10个格子
±-±-±-+
| | | |
±-±-±-±-+
| | | | |
±-±-±-±-+
| | | |
±-±-±-+

(如果显示有问题,也可以参看【图1.jpg】)

填入0~9的数字。要求:连续的两个数字不能相邻。
(左右、上下、对角都算相邻)

一共有多少种可能的填数方案?
在这里插入图片描述

#include<iostream>
#include<math.h>
using namespace std;
int map[4][5]={-2};
int vis[10]={0};
int ans=0;

void dfs(int x,int y)
{
	if(x==3 && y==3)
	{
		ans++;
		return;
	}
	for(int i=0; i<=9; i++)
	{
		if(vis[i]) continue;
		if(x==1)
		{
			if(abs(map[x][y-1]-i) == 1)
				continue;
		}
		else
		{
			int a = abs(map[x-1][y]-i);
			int b = abs(map[x-1][y-1]-i);
			int c = abs(map[x][y-1]-i);
			int d = abs(map[x-1][y+1]-i);
			if(a==1 || b==1 || c==1 ||d==1)
				continue; 
		}
		vis[i]=1;
		map[x][y]=i;
		if(y==4)
			dfs(x+1,1);
		else
			dfs(x,y+1);
		vis[i]=0;
	}
}

int main()
{
	dfs(1,2);
	cout<<ans<<endl;
	return 0;
} 

9、寒假作业
每个方块代表1~13中的某一个数字,但不能重复。
在这里插入图片描述
比如:
6 + 7 = 13
9 - 8 = 1
3 * 4 = 12
10 / 2 = 5

以及:
7 + 6 = 13
9 - 8 = 1
3 * 4 = 12
10 / 2 = 5

就算两种解法。(加法,乘法交换律后算不同的方案)

你一共找到了多少种方案?

#include<iostream>
using namespace std;
int vis[14]={0};
int a[12]={0};
int ans=0; 

void dfs(int n)
{
	if(n==3)
	{
		if(a[0]+a[1] != a[2])
			return; 
	}
	if(n==6)
	{
		if(a[3]-a[4] != a[5])
			return;
	}
	if(n==9)
	{
		if(a[6]*a[7] != a[8])
			return;
	}
	if(n>11)
	{
		if(a[9]/a[10] == a[11])
			ans++;
		return;	
	}
	
	for(int i=1; i<=13; i++)
	{
		if(!vis[i])
		{
			vis[i]=1;
			a[n]=i;
			dfs(n+1);
			vis[i]=0;
		} 
	}
	
} 

int main()
{
	dfs(0);
	cout<<ans<<endl;
	return 0;
}

10、密码脱落

X星球的考古学家发现了一批古代留下来的密码。
这些密码是由A、B、C、D 四种植物的种子串成的序列。
仔细分析发现,这些密码串当初应该是前后对称的(也就是我们说的镜像串)。
由于年代久远,其中许多种子脱落了,因而可能会失去镜像的特征。

你的任务是:
给定一个现在看到的密码串,计算一下从当初的状态,它要至少脱落多少个种子,才可能会变成现在的样子。

输入一行,表示现在看到的密码串(长度不大于1000)
要求输出一个正整数,表示至少脱落了多少个种子。

例如,输入:
ABCBA
则程序应该输出:
0

再例如,输入:
ABDCDCBABC
则程序应该输出:
3

资源约定:
峰值内存消耗 < 256M
CPU消耗 < 1000ms

#include<iostream>
using namespace std;

int dfs(char* start,char* end)
{
	if(start>=end)
		return 0;
	else 
	{
		if(*start == *end)
			return dfs(start+1,end-1);
		else
		{
			return min(1+dfs(start,end-1),1+dfs(start+1,end));
		}
			
	}
		
}

int main()
{
	string str;
	cin>>str;
	
	char *p1,*p2;
	p1 = &str[0];
	p2 = &str[str.length()-1];
	
	int ans=dfs(p1,p2);
	cout<<ans<<endl;
} 

11.随意组合
小明被绑架到X星球的巫师W那里。

其时,W正在玩弄两组数据 (2 3 5 8) 和 (1 4 6 7)
他命令小明从一组数据中分别取数与另一组中的数配对,共配成4对(组中的每个数必被用到)。
小明的配法是:{(8,7),(5,6),(3,4),(2,1)}

巫师凝视片刻,突然说这个配法太棒了!

因为:
每个配对中的数字组成两位数,求平方和,无论正倒,居然相等:
87^2 + 56^2 + 34^2 + 21^2 = 12302
78^2 + 65^2 + 43^2 + 12^2 = 12302

小明想了想说:“这有什么奇怪呢,我们地球人都知道,随便配配也可以啊!”
{(8,6),(5,4),(3,1),(2,7)}

86^2 + 54^2 + 31^2 + 27^2 = 12002
68^2 + 45^2 + 13^2 + 72^2 = 12002

巫师顿时凌乱了。。。。。

请你计算一下,包括上边给出的两种配法,巫师的两组数据一共有多少种配对方案具有该特征。
配对方案计数时,不考虑配对的出现次序。
就是说:
{(8,7),(5,6),(3,4),(2,1)}

{(5,6),(8,7),(3,4),(2,1)}
是同一种方案。

#include<iostream>
using namespace std;
#include<math.h>
int vis[4]={0};
int a[]={2,3,5,8};
int b[]={1,4,6,7};
int c[4]={0};
int ans=0;

int check()
{
	int sum1=0,sum2=0;
	for(int i=0; i<4; i++)
	{
		sum1+=pow(a[i]*10+c[i],2);
		sum2+=pow(a[i]+c[i]*10,2);
	}
	if(sum1==sum2)
		return 1;
	else
		return 0;
}

void dfs(int n)
{
	if(n>3)
	{
		if(check())
			ans++;
		return;
	}
	for(int i=0; i<4; i++)
	{
		if(!vis[i])
		{
			vis[i]=1;
			c[n]=b[i];
			dfs(n+1);
			vis[i]=0;
			c[n]=0;
		}
	}
}

int main()
{
	dfs(0);
	cout<<ans<<endl; 
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值