算法与数据结构--深搜与广搜

深度优先搜索练习

# 全排列问题

题目描述

按照字典序输出自然数 1 1 1 n n n 所有不重复的排列,即 n n n 的全排列,要求所产生的任一数字序列中不允许出现重复的数字。

输入格式

一个整数 n n n

输出格式

1 ∼ n 1 \sim n 1n 组成的所有不重复的数字序列,每行一个序列。

每个数字保留 5 5 5 个场宽。

样例 #1

样例输入 #1

3

样例输出 #1

    1    2    3
    1    3    2
    2    1    3
    2    3    1
    3    1    2
    3    2    1

提示

1 ≤ n ≤ 9 1 \leq n \leq 9 1n9

分析

我们以N=3为例,构造一棵搜索树(或说是状态树)来进行搜索。

同时构造出三个格子,用来存放搜索树中的结果。

现在,我们从第一格开始搜索。第一格填1的搜索树如下:

所以N=3的情况下,第一格填1的排列情况共有两种123,132.

第一格填2的搜索树如下:
请添加图片描述

所以N=3的情况下,第一格填2的排列情况共有两种 213,231.
请添加图片描述

代码

#include<iostream>
#include<cstdio>
using namespace std;
int a[500];
int b[500];
int n;
void dfs(int x){//x代表第几层
	if(x == n){
		for(int i=0;i<x;i++){
			printf("%5d",a[i]);
		}
		printf("\n");
		return;
	}
	for(int i=1;i<=n;i++){
		if(!b[i]){
			b[i] = 1;//标记
			a[x] = i;
			dfs(x+1);//递归
			b[i] = 0;//回溯
		}
	}
}
int main()
{
	cin>>n;
	dfs(0);
	
	return 0; 
}

# 组合的输出

题目描述

排列与组合是常用的数学方法,其中组合就是从 n n n 个元素中抽出 r r r 个元素(不分顺序且 r ≤ n r \le n rn),我们可以简单地将 n n n 个元素理解为自然数 1 , 2 , … , n 1,2,\dots,n 1,2,,n,从中任取 r r r 个数。

现要求你输出所有组合。

例如 n = 5 , r = 3 n=5,r=3 n=5,r=3,所有组合为:

123 , 124 , 125 , 134 , 135 , 145 , 234 , 235 , 245 , 345 123,124,125,134,135,145,234,235,245,345 123,124,125,134,135,145,234,235,245,345

输入格式

一行两个自然数 n , r ( 1 < n < 21 , 0 ≤ r ≤ n ) n,r(1<n<21,0 \le r \le n) n,r(1<n<21,0rn)

输出格式

所有的组合,每一个组合占一行且其中的元素按由小到大的顺序排列,每个元素占三个字符的位置,所有的组合也按字典顺序。

注意哦!输出时,每个数字需要 3 3 3 个场宽。以 C++ 为例,你可以使用下列代码:

cout << setw(3) << x;

输出占 3 3 3 个场宽的数 x x x。注意你需要头文件 iomanip

样例 #1

样例输入 #1

5 3

样例输出 #1

1  2  3
  1  2  4
  1  2  5
  1  3  4
  1  3  5
  1  4  5
  2  3  4
  2  3  5
  2  4  5
  3  4  5

分析

组合与排列的区别在于组合里每一个数都要大于前一个数。

代码

#include<iostream>
#include<cstdio>
using namespace std;
int a[500];
int b[500];
int n,r;
void dfs(int x){
    if(x > r){
        for(int i=1;i<x;i++){
            printf("%3d",a[i]);
        }
        printf("\n");
        return;
    }
    for(int i=a[x-1]+1;i<=n;i++){ //因为后一个数要比前面的大,所以就不能一直从1开始
        a[x] = i;
        dfs(x+1);
    }
}
int main()
{
    cin>>n>>r;
    dfs(1);
     
    return 0; 
}

注意

①组合的数字不能有重复!!!

②后面的数字要比前面大!!!

③输出时一定要带上场宽!!!

④输完后一定要记得换行!!!

⑤递归后一定要状态回溯!!!

有重复元素的排列问题

问题描述

设集合R={r1,r2,…,rn}是要进行排列的n个元素,其中r1,r2,…,rn可能相同。 试着设计一个算法,列出R的所有不同排列。 即,给定n以及待排的n个可能重复的元素。计算输出n个元素的所有不同排列。

算法设计

给定n及待排列的n个元素,计算出这n个元素的所有不同排列。

数据输入

第1行是元素个数n,1<=n<=500。接下来的1行是待排列的n个元素

结果输出

程序运行结束时,将计算输出n个元素的所有不同排列。最后1行中的数是排列总数。

输入样例

输入:

4
aacc

输出:

aacc
acac
acca
caac
caca
ccaa
6

分析

拆分字符串,进行递归回溯

代码

#include<bits/stdc++.h>
using namespace std;
char a[251];
int n[27],s,sum=0;
void f(int k) {
	if(k==s+1) {
		for(int i=1;i<=s;i++)
			printf("%c",a[i]);
		printf("\n");
		sum++;
		return ;
	}
	for(int i=1;i<=26;i++)//从字母a找到字母z 
	{
		if(n[i]!=0)
		{
			n[i]--;
			a[k]=char(i+96);
			f(k+1);//下一个字符 
			n[i]++;
		}
		else
			continue;
	}
}
int main() {
	scanf("%d",&s);
	for(int i=1; i<=s; i++) {
		cin>>a[i];
		n[a[i]-96]++;//每个字符的数量
	}
	f(1);
	printf("%d",sum);
	return 0;
}

排列棋子

将M个白棋子与N个黑棋子排成一行,可以排成多种不同的图案。例如:2个白棋子和2个黑棋子,一共可以排成如下图所示的6种图案(根据组合数计算公式:)

说明: http://10.60.64.213:8080/JudgeOnline/images/4736_2.bmp

请你编写一段程序,输出M个白棋子与N个黑棋子能够组成的所有图案。

为了避免程序输出结果过多导致严重超时,特别限制:1≤M,N≤6
输入
两个正整数M,N表示白棋子与黑棋子的数量,并且满足1≤M,N≤6
输出
M个白棋子与N个黑棋子可以排列的所有图案。
要求:每行输出一种图案,白棋子用0表示,黑棋子用1表示,按升序输出
样例输入
2 1
2 2
2 3
样例输出
001
010
100

0011
0101
0110

1001
1010
1100

00111
01011
01101
01110
10011
10101
10110
11001
11010
11100

代码

#include<bits/stdc++.h>
using namespace std;

int a[100];
bool b[100];
int n,m,t;
void dfs(int x){
	if(n+m== 0){
		for(int i=1;i<=t;i++) cout<<a[i];
		cout<<endl;
	}
	if(b[x] == false){
		b[x]=true;
		if(m){
			a[x] = 0;
			m--;
			dfs(x+1);
			m++;
			b[x]=false;
		}
		if(n){
			a[x] = 1;
			n--;
			dfs(x+1);
			n++;
			b[x]=false;
			
		}
	}
}
int main()
{
	while(cin >> m >> n ){
		t = m+n;
		memset(a,-1,sizeof(a));
		memset(b,false,sizeof(b));
		dfs(1);
		cout<<endl;		
	}
	return 0;

}

# 自然数的拆分问题

题目描述

任何一个大于 1 1 1 的自然数 n n n,总可以拆分成若干个小于 n n n 的自然数之和。现在给你一个自然数 n n n,要求你求出 n n n 的拆分成一些数字的和。每个拆分后的序列中的数字从小到大排序。然后你需要输出这些序列,其中字典序小的序列需要优先输出。

输入格式

输入:待拆分的自然数 n n n

输出格式

输出:若干数的加法式子。

样例 #1

样例输入 #1

7

样例输出 #1

1+1+1+1+1+1+1
1+1+1+1+1+2
1+1+1+1+3
1+1+1+2+2
1+1+1+4
1+1+2+3
1+1+5
1+2+2+2
1+2+4
1+3+3
1+6
2+2+3
2+5
3+4

提示

数据保证, 2 ≤ n ≤ 8 2\leq n\le 8 2n8

分析

拆分5=2+2+15=2+1+1+1为例。首先在完成前一组数值拆分恢复现场,回溯穷举至2(第一层),得第一个拆分数值2,余下的数值5-2,即为3,剩下数值3与已拆分前一个所得数值比较取较小值2(避免5再次拆分成2和3),获得拆分数值穷举范围即2-1,进入第二层搜索,首先是穷举2,获得第二个拆分数值2,计算剩余数值得1,然后递归调拆分余下的数值1,首先判断剩下的数值是否为0(完成拆分),不是,该数值与前一个拆分所得的数值2比较取小数值得1,获得该数的数值拆分范围为1-1,进入第三层搜索,取第三个拆分数值1,同时剩下的数值减去该数值得0,递归调用,拆分余下的数值0,在递归调用过程中,发现剩余数值为0,递归调用结束,即打印该拆分数即4=2+2+1,恢复现场至上一层,得剩余数值为1,再次恢复现场至上一层(第二层),得剩余数值为3,穷举下一个拆分数值1,据悉完成5=1+1+1+1+1的拆分。

代码

#include<iostream>
using namespace std;
const int N = 22;
int path[N],len = 0,n;

void dfs(int x){//x代表剩余 
	//结束条件
	if(x ==0 && len>1){
		for(int i=1;i<len;i++){
			cout<<path[i]<<"+";
		}
		cout<<path[len]<<endl;
		return ;
	}  
	for(int i=path[len];i<=x;i++){  
		if(x-i>=0){
			path[++len] = i;
			//cout<<"i="<<i<<" dfs("<<x<<"-"<<i<<")"<<" "<<len<<endl;
			dfs(x-i);
			len--;
		}
	}
}
int main()
{
	cin>>n;
	path[0]=1;
	dfs(n);
	
	return 0;
}


BFS经典例题练习

# 奇怪的电梯

题目描述

呵呵,有一天我做了一个梦,梦见了一种很奇怪的电梯。大楼的每一层楼都可以停电梯,而且第 i i i 层楼( 1 ≤ i ≤ N 1 \le i \le N 1iN)上有一个数字 K i K_i Ki 0 ≤ K i ≤ N 0 \le K_i \le N 0KiN)。电梯只有四个按钮:开,关,上,下。上下的层数等于当前楼层上的那个数字。当然,如果不能满足要求,相应的按钮就会失灵。例如: 3 , 3 , 1 , 2 , 5 3, 3, 1, 2, 5 3,3,1,2,5 代表了 K i K_i Ki K 1 = 3 K_1=3 K1=3 K 2 = 3 K_2=3 K2=3,……),从 1 1 1 楼开始。在 1 1 1 楼,按“上”可以到 4 4 4 楼,按“下”是不起作用的,因为没有 − 2 -2 2 楼。那么,从 A A A 楼到 B B B 楼至少要按几次按钮呢?

输入格式

共二行。

第一行为三个用空格隔开的正整数,表示 N , A , B N, A, B N,A,B 1 ≤ N ≤ 200 1 \le N \le 200 1N200 1 ≤ A , B ≤ N 1 \le A, B \le N 1A,BN)。

第二行为 N N N 个用空格隔开的非负整数,表示 K i K_i Ki

输出格式

一行,即最少按键次数,若无法到达,则输出 -1

样例 #1

样例输入 #1

5 1 5
3 3 1 2 5

样例输出 #1

3

提示

对于 100 % 100 \% 100% 的数据, 1 ≤ N ≤ 200 1 \le N \le 200 1N200 1 ≤ A , B ≤ N 1 \le A, B \le N 1A,BN 0 ≤ K i ≤ N 0 \le K_i \le N 0KiN

分析

这道题方向只有两个,要么上要么下,所以只需要用广搜,将每个楼层向上和向下的情况都遍历一
遍,用step来记录按钮次数,直到找到相应的楼层,如果所有的节点都遍历完了还没找到相应的楼层,说明到不了,输出-1。

代码

#include <bits/stdc++.h>
#include <queue>
using namespace std;

int n,a,b;
int k[210]/* 当前楼层的数字*/,s[210]/*判断是否到达过*/;
void bfs(int a,int b);//广搜 
int main(){
   cin>>n>>a>>b;
   
   for(int i=1;i<=n;i++) cin>>k[i];
   bfs(a,b);
   cout<< s[b];//输出到达当前楼层所用次数 
    return 0;
}
queue <int> q;
void bfs(int a,int b){
	memset(s,-1,sizeof s);//初始化s为-1 方便不能到达时输出 
	q.push(a);
	s[a] = 0;
	while(!q.empty()){
		int x = q.front();
		q.pop();
		int  f = x + k[x];//向上走 
		if( f<=n && s[f]==-1){
			q.push(f);
			s[f] = s[x] + 1;
			
		}
		f = x - k[x];//向下走 
		if(f>=1 && s[f]==-1){
			q.push(f);//下一次的起始位置 
			s[f] = s[x] + 1;//到达该楼层的次数加1 
		}
		 
	}
}

# 迷宫

题目描述

给定一个 N × M N \times M N×M 方格的迷宫,迷宫里有 T T T 处障碍,障碍处不可通过。

在迷宫中移动有上下左右四种方式,每次只能移动一个方格。数据保证起点上没有障碍。

给定起点坐标和终点坐标,每个方格最多经过一次,问有多少种从起点坐标到终点坐标的方案。

输入格式

第一行为三个正整数 N , M , T N,M,T N,M,T,分别表示迷宫的长宽和障碍总数。

第二行为四个正整数 S X , S Y , F X , F Y SX,SY,FX,FY SX,SY,FX,FY S X , S Y SX,SY SX,SY 代表起点坐标, F X , F Y FX,FY FX,FY 代表终点坐标。

接下来 T T T 行,每行两个正整数,表示障碍点的坐标。

输出格式

输出从起点坐标到终点坐标的方案总数。

样例 #1

样例输入 #1

2 2 1
1 1 2 2
1 2

样例输出 #1

1

提示

对于 100 % 100\% 100% 的数据, 1 ≤ N , M ≤ 5 1 \le N,M \le 5 1N,M5 1 ≤ T ≤ 10 1 \le T \le 10 1T10 1 ≤ S X , F X ≤ n 1 \le SX,FX \le n 1SX,FXn 1 ≤ S Y , F Y ≤ m 1 \le SY,FY \le m 1SY,FYm

分析

这题用深搜更好写,用广搜需要在每一个走到的点中,开一个数组来标记之前走过的位置,来找到不同的路径。

广搜代码

#include <bits/stdc++.h>
#include <queue>
using namespace std;
queue<int>q;
int dir[][2] = {
	{0,-1},{1,0},{0,1},{-1,0}
}; 
int mp[10][10],a[10][10];
int n,m,cnt=0,sx,sy,fx,fy,tx,ty,t;
void bfs(int x,int y);
int main()
{
	cin>>n>>m>>t;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			mp[i][j] = 1; //先排除障碍初始化迷宫都能走 
		}
	}
	cin>>sx>>sy>>fx>>fy;
	for(int i=1;i<=t;i++){
		cin>>tx>>ty;
		mp[tx][ty] = 0;  //将障碍处设为0方便后面判断 
	}
	bfs(sx,sy);
	cout<<cnt;//输出 
  
    return 0;
}
void bfs(int x,int y){
	if(x==fx&&y==fy){
		cnt++;
		return;//返回,继续搜索;
	}
	for(int i=0;i<4;i++){0——3是左,右,下,上四个方向;
		int nx = x + dir[i][0];//新行
		int ny = y + dir[i][1];//新列 
		if(a[nx][ny] ==0 && mp[nx][ny] ==1 ){
			a[x][y] = 1;//标记走过 
			bfs(nx,ny);//从新的位置开始走 
			a[x][y]=0;//还原状态 
		}
	}
}

深搜代码

#include<bits/stdc++.h>
using namespace std;
const int N = 10;
int a[N][N]={0};
int v[N][N]={0};
//方向数组(上下左右)
int dx[] = {-1, 1, 0, 0};
int dy[] = {0, 0, -1, 1};
int n, m, t;
int sx, sy, fx, fy;
int cnt = 0;
//判断是否在迷宫范围内
int check(int x,int y){
	if (x <= n && x >= 1 && y <= m && y >= 1){
	    return 1;
	}
	return 0;
}
void DFS(int x,int y){
	if(x == fx && y == fy){
		cnt++;
		return;
	}
	for (int i = 0; i < 4;i++){
	//下一个位置的坐标
		int tx = x + dx[i];
		int ty = y + dy[i];
		if (check(tx, ty)==1 && v[tx][ty] != 1 && a[tx][ty] != 1){
			v[tx][ty] = 1;
			DFS(tx, ty);
			v[tx][ty] = 0;//回溯,上一个位置恢复成没走过
		}
    }
}
int main()
{
	//输入迷宫长宽和障碍总数
	cin >> n >> m >> t;
	//起点和终点坐标
	cin >> sx >> sy >> fx >> fy;
	v[sx][sy] = 1;
	//输入t个障碍
	int x, y;
	while(t--){
		cin >> x >> y;
		a[x][y] = 1;//将障碍直接标记为走过,这样就不会走到障碍上去了
	}
	DFS(sx,sy);
	cout << cnt;
	return 0;
}

# 填涂颜色

题目描述

由数字 0 0 0 组成的方阵中,有一任意形状闭合圈,闭合圈由数字 1 1 1 构成,围圈时只走上下左右 4 4 4 个方向。现要求把闭合圈内的所有空间都填写成 2 2 2。例如: 6 × 6 6\times 6 6×6 的方阵( n = 6 n=6 n=6),涂色前和涂色后的方阵如下:

0 0 0 0 0 0
0 0 1 1 1 1
0 1 1 0 0 1
1 1 0 0 0 1
1 0 0 0 0 1
1 1 1 1 1 1
0 0 0 0 0 0
0 0 1 1 1 1
0 1 1 2 2 1
1 1 2 2 2 1
1 2 2 2 2 1
1 1 1 1 1 1

输入格式

每组测试数据第一行一个整数 n ( 1 ≤ n ≤ 30 ) n(1 \le n \le 30) n(1n30)

接下来 n n n 行,由 0 0 0 1 1 1 组成的 n × n n \times n n×n 的方阵。

方阵内只有一个闭合圈,圈内至少有一个 0 0 0

输出格式

已经填好数字 2 2 2 的完整方阵。

样例 #1

样例输入 #1

6
0 0 0 0 0 0
0 0 1 1 1 1
0 1 1 0 0 1
1 1 0 0 0 1
1 0 0 0 0 1
1 1 1 1 1 1

样例输出 #1

0 0 0 0 0 0
0 0 1 1 1 1
0 1 1 2 2 1
1 1 2 2 2 1
1 2 2 2 2 1
1 1 1 1 1 1

提示

对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 30 1 \le n \le 30 1n30

分析

因为我们很难判断那些为0的点在圆内还是在圆外,所以我们可以反向操作,先将所有值为0的点染色成2,然后在以最外圈每一个点为起点进行搜索,将圆外面的点颜色染回来就行。

代码

#include<iostream>
using namespace std;
int a[35][35],b[35][35];
int dir[][2] = {
	{-1,0},{0,1},{1,0},{0,-1}
	
};

int n;

void bfs(int x,int y){
	if(x<0 || x>n+1 || y<0 || y>n+1 || b[x][y] != 0 ){//如果搜过头或者已经被搜过了或者本来就是墙的就往回
		return;
	}
	b[x][y] = 1;
	for(int i=0;i<4;i++){
		int nx = x+dir[i][0];
		int ny = y+dir[i][1];
		bfs(nx,ny);
	}
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			cin>>a[i][j];
		    if(a[i][j] == 0) b[i][j] =0;
		    else b[i][j] =2;//
		    
		}
	}
	bfs(0,0);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			
		    if(b[i][j] == 0) cout<<2<<" ";//如果染过色以后i,j那个地方还是0,说明没有搜到,就是周围有墙,当然就是被围住了,然后输出2
		    else cout<<a[i][j]<<" ";//因为被染色了,本来没有被围住的水和墙都染成了1,所以就输出a[i][j]

		   
		}
		 cout<<endl;
    }
	
	return 0;
}

I - Red and Black

题目

There is a rectangular room, covered with square tiles. Each tile is colored either red or black. A man is standing on a black tile. From a tile, he can move to one of four adjacent tiles. But he can’t move on red tiles, he can move only on black tiles.

Write a program to count the number of black tiles which he can reach by repeating the moves described above.
题意概括 :红与黑,#代表红色格子,.代表黑色格子,@代表人,人只能走黑色格子而不能走红色格子,问人一共能走多少个格子.

Input

The input consists of multiple data sets. A data set starts with a line containing two positive integers W and H; W and H are the numbers of tiles in the x- and y- directions, respectively. W and H are not more than 20.

There are H more lines in the data set, each of which includes W characters. Each character represents the color of a tile as follows.

‘.’ - a black tile
‘#’ - a red tile
‘@’ - a man on a black tile(appears exactly once in a data set)
The end of the input is indicated by a line consisting of two zeros.

Output

For each data set, your program should output a line which contains the number of tiles he can reach from the initial tile (including itself).

Sample Input

6 9

....#.

.....#

......

......

......

......

......

#@...#

.#..#.

11 9

.#.........

.#.#######.

.#.#.....#.

.#.#.###.#.

.#.#..@#.#.

.#.#####.#.

.#.......#.

.#########.

...........

11 6

..#..#..#..

..#..#..#..

..#..#..###

..#..#..#@.

..#..#..#..

..#..#..#..

7 7

..#.#..

..#.#..

###.###

...@...

###.###

..#.#..

..#.#..

0 0

Sample Output

45

59

6

13

分析

解题思路 :首先找到人所在位置的坐标,然后向四周扩散,记录所能走的点的个数,知道不能走为止.

代码

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

char a[25][25];//地图 
//搜索方向数组
int dir[][2] = {{0,-1},{-1,0},{0,1},{1,0}};//左上右下 
int n,m;
struct pos{
	int r,c;
	//构造方法
	
}p;
//从起点p开始搜索,返回黑砖的数量
int bfs(pos p); 
int main()
{
	pos st;//起点 
	while(cin>>m>>n){//输入地图信息 
	if(!m && !n) break;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>a[i][j];
			if(a[i][j] == '@'){//找到起点 
				st.r = i;
				st.c = j;
			}
		}
	}
	//广搜
	 cout<<bfs(st)<<endl;
    }  
   
	return 0;
}
int bfs(pos p){
	queue<pos> q;
	q.push(p);//起点入队并标记红砖 
	a[p.r][p.c] = '#';
	int cnt = 1;//搜到的黑砖数量 
	while(!q.empty()){
		//取队首并出队
		pos p1 = q.front();
		q.pop();
		//搜索(左上右下) 
		for(int i=0;i<4;i++){
			int nr = p1.r + dir[i][0];//新行 
			int nc = p1.c + dir[i][1];//新列 
			//(nr,nc)在地图范围内,并且是黑砖 
			if(nr >= 1 && nr <= n && nc >= 1 && nc <= m && a[nr][nc] == '.'){
				cnt++;
				p.c = nc;
				p.r = nr;
				q.push(p);
				a[nr][nc] = '#';
			}
		}
	}
	return cnt;
} 

搜索综合练习

# 最短路计数

题目描述

给出一个 N N N 个顶点 M M M 条边的无向无权图,顶点编号为 1 ∼ N 1\sim N 1N。问从顶点 1 1 1 开始,到其他每个点的最短路有几条。

输入格式

第一行包含 2 2 2 个正整数 N , M N,M N,M,为图的顶点数与边数。

接下来 M M M 行,每行 2 2 2 个正整数 x , y x,y x,y,表示有一条由顶点 x x x 连向顶点 y y y 的边,请注意可能有自环与重边。

输出格式

N N N 行,每行一个非负整数,第 i i i 行输出从顶点 1 1 1 到顶点 i i i 有多少条不同的最短路,由于答案有可能会很大,你只需要输出 $ ans \bmod 100003$ 后的结果即可。如果无法到达顶点 i i i 则输出 0 0 0

样例 #1

样例输入 #1

5 7
1 2
1 3
2 4
3 4
2 3
4 5
4 5

样例输出 #1

1
1
1
2
4

提示

1 1 1 5 5 5 的最短路有 4 4 4 条,分别为 2 2 2 1 → 2 → 4 → 5 1\to 2\to 4\to 5 1245 2 2 2 1 → 3 → 4 → 5 1\to 3\to 4\to 5 1345(由于 4 → 5 4\to 5 45 的边有 2 2 2 条)。

对于 20 % 20\% 20% 的数据, 1 ≤ N ≤ 100 1\le N \le 100 1N100
对于 60 % 60\% 60% 的数据, 1 ≤ N ≤ 1 0 3 1\le N \le 10^3 1N103
对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 1 0 6 1\le N\le10^6 1N106 1 ≤ M ≤ 2 × 1 0 6 1\le M\le 2\times 10^6 1M2×106

分析

因为所有的边权都为1,所以一个点的最短路就相当于是它在BFS搜索树中的深度。一个点最短路一定经过了一个层数比它少一的结点(否则不是最短路)。所以用每个相邻且层数比当前结点层数少一的点更新当前点的路径跳数即可。

代码

#include <bits/stdc++.h>
#include <queue>
#include<vector>

#define Mod 100003
using namespace std;
const int N =  1000005;

int dep[N],cnt[N];//dep是每个点对于源点的深度,cnt储存答案,即到点i的最短路个数
vector<int> mp[N];//注意这是2维数组,用来存每一个点所连的边,而题目中有10^6个点,所以是10^6+5
bool vis[N];//判断点是否使用
queue< int > q;
void bfs(int x);
int main()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=m;i++) {
		int x,y;
		cin>>x>>y;
		mp[x].push_back(y);//存边,无向图存有向边两次
		mp[y].push_back(x);
		
	}
	
	bfs(1);
	for(int i=1;i<=n;i++){
		
		cout<<cnt[i]<<endl;
	}
	return 0;
}
void bfs(int x){
	dep[x] = 0;
    vis[x] = 1;
	q.push(x);
	cnt[x] = 1;
	while(!q.empty()){
		int a = q.front();
		q.pop();
		for(int i=0;i<mp[a].size();i++){//遍历点所连的每一条边
			int t=mp[a][i];
			
			if(!vis[t]){
				vis[t] = 1;
				dep[t] = dep[a] + 1;//深度+1
				q.push(t);
			}
			
			if(dep[t] == dep[a] + 1)//这个点直接从旁边深度-1的结点添加最短路数量
			    cnt[t] = (cnt[t] + cnt[a])%Mod;
		}
	
	}
}

# 八数码难题

题目描述

3 × 3 3\times 3 3×3 的棋盘上,摆有八个棋子,每个棋子上标有 1 1 1 8 8 8 的某一数字。棋盘中留有一个空格,空格用 0 0 0 来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为 123804765 123804765 123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。

输入格式

输入初始状态,一行九个数字,空格用 0 0 0 表示。

输出格式

只有一行,该行只有一个数字,表示从初始状态到目标状态需要的最少移动次数。保证测试数据中无特殊无法到达目标状态数据。

样例 #1

样例输入 #1

283104765

样例输出 #1

4

提示

样例解释

图中标有 0 0 0 的是空格。绿色格子是空格所在位置,橙色格子是下一步可以移动到空格的位置。如图所示,用四步可以达到目标状态。

并且可以证明,不存在更优的策略。

分析

map去重,在这里只需要一个队列,因为需要较少步数达到的状态一定在步数较多的状态前入队列

代码

#include<iostream>
#include<map>
#include<queue>
#include<algorithm>
#define ll long long
using namespace std;
const ll dx[]={-1,0,0,1},dy[]={0,-1,1,0};//转移数组;
ll n;
int main()
{
    cin>>n;
    queue<ll> q;
    q.push(n);
    map<ll,ll> m;
    m[n]=0;
    while(!q.empty())
    {
        int u=q.front(); //初始状态入队列
        int c[3][3],f=0,g=0,n=u;
		q.pop();
        if(u==123804765) break;
        for(ll i=2;i>=0;i--)
            for(ll j=2;j>=0;j--){
                c[i][j]=n%10;
				n/=10;
                if(!c[i][j]){
                	f=i;
			    	g=j;
				}
				
            }
        for(ll i=0;i<4;i++)
        {
            ll nx=f+dx[i];
			ll ny=g+dy[i];
			ll ns=0;
            if(nx<0||ny<0||nx>2||ny>2) continue; //越界就不执行
            swap(c[nx][ny],c[f][g]);
            for(int i=0;i<3;i++)
                for(int j=0;j<3;j++)ns=ns*10+c[i][j];//矩阵转数列 
            if(!m.count(ns)) //ns之前在map中没出现过 
            {
                m[ns]=m[u]+1;//map去重的同时顺便统计到达这个状态所需的步数
                q.push(ns);
            }
            swap(c[nx][ny],c[f][g]);//状态复原
        }
    }
    cout<<m[123804765]<<endl; // map的下标直接用数列表示
    return 0;
}

# [NOIP2002 普及组] 选数

题目描述

已知 n n n 个整数 x 1 , x 2 , ⋯   , x n x_1,x_2,\cdots,x_n x1,x2,,xn,以及 1 1 1 个整数 k k k k < n k<n k<n)。从 n n n 个整数中任选 k k k 个整数相加,可分别得到一系列的和。例如当 n = 4 n=4 n=4 k = 3 k=3 k=3 4 4 4 个整数分别为 3 , 7 , 12 , 19 3,7,12,19 3,7,12,19 时,可得全部的组合与它们的和为:

3 + 7 + 12 = 22 3+7+12=22 3+7+12=22

3 + 7 + 19 = 29 3+7+19=29 3+7+19=29

7 + 12 + 19 = 38 7+12+19=38 7+12+19=38

3 + 12 + 19 = 34 3+12+19=34 3+12+19=34

现在,要求你计算出和为素数共有多少种。

例如上例,只有一种的和为素数: 3 + 7 + 19 = 29 3+7+19=29 3+7+19=29

输入格式

第一行两个空格隔开的整数 n , k n,k n,k 1 ≤ n ≤ 20 1 \le n \le 20 1n20 k < n k<n k<n)。

第二行 n n n 个整数,分别为 x 1 , x 2 , ⋯   , x n x_1,x_2,\cdots,x_n x1,x2,,xn 1 ≤ x i ≤ 5 × 1 0 6 1 \le x_i \le 5\times 10^6 1xi5×106)。

输出格式

输出一个整数,表示种类数。

样例 #1

样例输入 #1

4 3
3 7 12 19

样例输出 #1

1

分析

数据规模小所以直接深搜即可。

代码

#include<bits/stdc++.h>

using namespace std;
int n,k;
const int N = 5e6+5;
int a[N]/*存数组*/,tag[N],vis[N]/*标记是否被访问过*/,sum/*记录种类个数*/;
int prime(int x){//判断是否为素数 
	int t = sqrt(x),i;
	for( i=2;i<=t;i++){
		if(x % i == 0) return 0;	
	}
	
	return 1; 
}
void dfs(int step,int last){
	if(step == k){ //循环到了k个数字 
		int ans = 0;
		for(int i=0;i<k;i++){
			ans += a[tag[i]];
		}
		if(prime(ans)){
			
			sum ++;
			
		}
		return ;
	}
	for(int i = last;i<=n;i++){
		if( !vis[i] ){
			vis[i] = 1;
			tag[step] = i;
		//	cout<<"dfs("<<step<<"+"<<1<<" )"<<i;
		//	cout<<endl; 
			dfs(step + 1, i);
			vis[i] = 0;
			
		}
	}
}
int main()
{
	cin>>n>>k;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	dfs(0,1);
	cout<<sum<<endl;
  
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值