学习笔记——回溯算法

基本理论

回溯和递归的区别

1.递归是程序实现的方式,回溯依托于递归实现的算法。

2.递归关注的是程序能否实现,回溯关注的是某一类问题的解决。

(算法之所以能被称为算法,因为其适用于解决某一类问题。)

如何理解回溯算法

在具体的问题场景中有一棵问题搜索树,如果问题能被抽象化为一棵问题搜索树,那么便可以在这上进行深搜遍历,这时使用的就是回溯算法。

回溯算法设计心法

1.脑中浮现:问题状态搜索树

2.切勿妄想一步到位:先实现,再优化

3.搜索剪枝记心间:无招胜有招(针对于具体问题场景)

例题

1.八皇后问题

题目描述

一个如下的 6 × 6 6 \times 6 6×6 的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行、每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子。

上面的布局可以用序列 2   4   6   1   3   5 2\ 4\ 6\ 1\ 3\ 5 2 4 6 1 3 5 来描述,第 i i i 个数字表示在第 i i i 行的相应位置有一个棋子,如下:

行号 1   2   3   4   5   6 1\ 2\ 3\ 4\ 5\ 6 1 2 3 4 5 6

列号 2   4   6   1   3   5 2\ 4\ 6\ 1\ 3\ 5 2 4 6 1 3 5

这只是棋子放置的一个解。请编一个程序找出所有棋子放置的解。
并把它们以上面的序列方法输出,解按字典顺序排列。
请输出前 3 3 3 个解。最后一行是解的总个数。

输入格式

一行一个正整数 n n n,表示棋盘是 n × n n \times n n×n 大小的。

输出格式

前三行为前三个解,每个解的两个数字之间用一个空格隔开。第四行只有一个数字,表示解的总数。

样例 #1

样例输入 #1

6

样例输出 #1

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

提示

【数据范围】
对于 100 % 100\% 100% 的数据, 6 ≤ n ≤ 13 6 \le n \le 13 6n13

题目翻译来自NOCOW。

USACO Training Section 1.5

优化方案:
1.状态压缩,使用位而不是数组来表示哪一个位置使用过,对空间进行优化
在这里插入图片描述
2.快速枚举,(t&-t)表示第一个没有被访问过的位置
3.斜边表示,正斜边为i-j+1,反斜边为n-i+j。
代码实现

#include<iostream>
#include<unordered_map>
using namespace std;
#define MASK(n) ((1<<(n+1))-2)//掩码设置 
int total=3;
int arr[20];
int ans=0;
int n;
unordered_map<int,int>ind;
void output(int *arr)
{
	for(int i=1;i<=n;i++)
	{
		cout<<arr[i];
		if(i!=n)cout<<" ";
	}
	cout<<endl;
	total--;
	return ;
}
void dfs(int i,int t1,int t2,int t3)
{
	if(i>n)
	{
		ans++;
		if(total)output(arr);
		return ;
	}
	for(int k=t1;k;k-=(k&-k))
	{
		int j=ind[k&-k];
		if((t2&(1<<(i+j-1)))&&(t3&(1<<(i-j+n))))//不在斜线上 
		{
			arr[i]=j;
			dfs(i+1,t1-(1<<j),t2-(1<<(i+j-1)),t3-(1<<(i-j+n)));
		}
	}
	return ;
}
int main()
{
	cin>>n;
	int t1=MASK(n);
	int t2=MASK(2*n-1);
	int t3=MASK(2*n-1);
	for(int i=0;i<=2*n;i++)ind[1<<i]=i;
	//使用哈希表进行二进制值与标号的映射 
	dfs(1,t1,t2,t3);
	cout<<ans;
	return 0;
 } 

2.奇怪的电梯

题目描述

呵呵,有一天我做了一个梦,梦见了一种很奇怪的电梯。大楼的每一层楼都可以停电梯,而且第 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

本题共 16 16 16 个测试点,前 15 15 15 个每个测试点 6 6 6 分,最后一个测试点 10 10 10 分。

本题的问题状态搜索树
优化方案

历史答案剪枝,设置dis数组记录起点到每一个点的最短距离,如果接下来遇到比它大的直接return。

代码实现

#include<iostream>
using namespace std;
#define MAXSIZE 200
int arr[MAXSIZE+5];
int dis[MAXSIZE+5]={0};
int n,a,b;
int ans=0x7fffffff;
void dfs(int a,int b,int cnt)
{
	if(dis[a]<=cnt)return ;
	dis[a]=cnt;
	if(a==b)
	{
		ans=min(ans,cnt);
		return ;
	}
		if(a-arr[a]>0)dfs(a-arr[a],b,cnt+1);
		if(a+arr[a]<=n)dfs(a+arr[a],b,cnt+1);
	return ;
}
int main()
{
	cin>>n>>a>>b;
	for(int i=1;i<=n;i++)cin>>arr[i];
	for(int i=1;i<=n;i++)dis[i]=n+1;
	dfs(a,b,0);
	if(ans==0x7fffffff)cout<<-1;
	else cout<<ans;
	return 0;
 } 

误区:不能直接把访问过的楼层去除以作剪枝条件,因为可能第一次访问到该楼层的次数不是最优。

3.选数

题目描述

已知 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<iostream>
using namespace std;
#define MAX_N 20
int arr[MAX_N+5];
long long ans=0;
bool is_prime(int sum)
{
	for(int i=2;i*i<=sum;i++)
	{
		if(sum%i==0)return 0;
	}
	return 1;
}
void dfs(int a,int b,int n,int k,int sum)
{
	if(a==k)
	{
		if(is_prime(sum))ans++;
		return ;
	}
	for(int i=b;i<=n;i++)
	{
		dfs(a+1,i+1,n,k,sum+arr[i]);
	}
	return ;
}
int main()
{
	int n,k;
	cin>>n>>k;
	int sum=0;
	for(int i=1;i<=n;i++)
	cin>>arr[i];
	dfs(0,1,n,k,sum);
	cout<<ans;
	return 0;
}

4.马的遍历

题目描述

有一个 n × m n \times m n×m 的棋盘,在某个点 ( x , y ) (x, y) (x,y) 上有一个马,要求你计算出马到达棋盘上任意一个点最少要走几步。

输入格式

输入只有一行四个整数,分别为 n , m , x , y n, m, x, y n,m,x,y

输出格式

一个 n × m n \times m n×m 的矩阵,代表马到达某个点最少要走几步(不能到达则输出 − 1 -1 1)。

样例 #1

样例输入 #1

3 3 1 1

样例输出 #1

0    3    2    
3    -1   1    
2    1    4

提示

数据规模与约定

对于全部的测试点,保证 1 ≤ x ≤ n ≤ 400 1 \leq x \leq n \leq 400 1xn400 1 ≤ y ≤ m ≤ 400 1 \leq y \leq m \leq 400 1ym400

代码实现1

#include<iostream>
#include<queue>
using namespace std;
#define MAXSIZE 400
int dis[MAXSIZE+5][MAXSIZE+5];
int pos[8][2]={{-2,-1},{-2,1},{-1,-2},{-1,2},{1,-2},{1,2},{2,-1},{2,1}};
struct Data
{
	int x,int y;
}
int x,y,n,m;
void bfs(int x,int y,int cnt)
{
	queue<Data>q;
	q.push(Data(x,y));
	
	if(dis[x][y]<=cnt)return ;
	dis[x][y]=cnt;
	for(int i=0;i<8;i++)
	{
		if(x+pos[i][0]>n||x+pos[i][0]<1)continue;
		if(y+pos[i][1]>m||y+pos[i][1]<1)continue;
		dfs(x+pos[i][0],y+pos[i][1],cnt+1);
	}
	return ;
}
int main()
{
	cin>>n>>m>>x>>y;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			dis[i][j]=0x7fffffff;
		}
	}
	dfs(x,y,0);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			if(dis[i][j]==0x7fffffff)cout<<-1;
			else cout<<dis[i][j];
			if(j!=m)cout<<" ";
		}
		cout<<endl;
	}
	return 0;
}

分析:如果使用bfs的话,那么第一次走到某一个位置一定是步数最少的情况,改用bfs可以大幅降低时间开销。

代码实现2

#include<iostream>
#include<queue>
using namespace std;
#define MAXSIZE 400
int dis[MAXSIZE+5][MAXSIZE+5];
int pos[8][2]={{-2,-1},{-2,1},{-1,-2},{-1,2},{1,-2},{1,2},{2,-1},{2,1}};
struct Data
{
	Data(int x,int y,int t):x(x),y(y),t(t){};
	int x,y,t;
};
int x,y,n,m;
void bfs(int x,int y,int t)
{
	queue<Data>q;
	q.push(Data(x,y,t));
	dis[x][y]=0;
	while(!q.empty())
	{
		Data item=q.front();
		q.pop();
		for(int i=0;i<8;i++)
		{
		int xx=item.x+pos[i][0];
		int yy=item.y+pos[i][1];
		int tt=item.t;
		if(xx>n||xx<1)continue;
		if(yy>m||yy<1)continue;
		if(dis[xx][yy]!=-1)continue;
		q.push(Data(xx,yy,tt+1));
		dis[xx][yy]=tt+1;
		}
	}
	return ;
}
int main()
{
	cin>>n>>m>>x>>y;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			dis[i][j]=-1;
		}
	}
	bfs(x,y,0);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			cout<<dis[i][j];
			if(j!=m)cout<<" ";
		}
		cout<<endl;
	}
	return 0;
}

5.迷宫

题目描述

给定一个 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<iostream>
using namespace std;
int pos[4][2]={{0,1},{1,0},{-1,0},{0,-1}};
#define MAX_T 10
#define MAX_N 5
int N,M,T;
int g[MAX_N+5][MAX_N+5]={0};
int ans=0;
void dfs(int x,int y,int fx,int fy)
{
	if(x==fx&&y==fy)
	{
		ans++;
		return ;
	}
	g[x][y]=0; 
	for(int i=0;i<4;i++)
	{
		int xx=x+pos[i][0];
		int yy=y+pos[i][1];
		if(g[xx][yy]==0)continue;
		dfs(xx,yy,fx,fy);
	}
	g[x][y]=1;//注意重置为0!!! 
	return ;
}
int main()
{
	cin>>N>>M>>T;
	int SX,SY,FX,FY;
	cin>>SX>>SY>>FX>>FY;
	for(int i=1;i<=N;i++)
	{
		for(int j=1;j<=M;j++)
		g[i][j]=1;
	}
	for(int i=1,a,b;i<=T;i++)
	{
		cin>>a>>b;
		g[a][b]=0;
	}
	dfs(SX,SY,FX,FY);
	cout<<ans;
	return 0;
}

6.吃奶酪

题目描述

房间里放着 n n n 块奶酪。一只小老鼠要把它们都吃掉,问至少要跑多少距离?老鼠一开始在 ( 0 , 0 ) (0,0) (0,0) 点处。

#输入格式

第一行有一个整数,表示奶酪的数量 n n n

2 2 2 到第 ( n + 1 ) (n + 1) (n+1) 行,每行两个实数,第 ( i + 1 ) (i + 1) (i+1) 行的实数分别表示第 i i i 块奶酪的横纵坐标 x i , y i x_i, y_i xi,yi

输出格式

输出一行一个实数,表示要跑的最少距离,保留 2 2 2 位小数。

样例 #1

样例输入 #1

4
1 1
1 -1
-1 1
-1 -1

样例输出 #1

7.41

提示

数据规模与约定

对于全部的测试点,保证 1 ≤ n ≤ 15 1\leq n\leq 15 1n15 ∣ x i ∣ , ∣ y i ∣ ≤ 200 |x_i|, |y_i| \leq 200 xi,yi200,小数点后最多有 3 3 3 位数字。

提示

对于两个点 ( x 1 , y 1 ) (x_1,y_1) (x1,y1) ( x 2 , y 2 ) (x_2, y_2) (x2,y2),两点之间的距离公式为 ( x 1 − x 2 ) 2 + ( y 1 − y 2 ) 2 \sqrt{(x_1-x_2)^2+(y_1-y_2)^2} (x1x2)2+(y1y2)2

思路:状态搜索树和排列型枚举相同,可以使用状态压缩进行优化,同时对最优历史答案进行记录来剪枝。

代码实现

#include<iostream>
#include<unordered_map>
#include<cmath>
#include<vector>
using namespace std;
#define MASK(n) ((1<<(n+1))-2)
#define MAX_N 15
#define S(a) ((a)*(a))
double dp[70000][20]={0};
int n;
unordered_map<int,int>ind;
struct Data{
	Data(double a,double b):x(a),y(b){}
	double x,y;
};
double DIS(double a,double b,double c,double d)
{
	return sqrt(S(a-c)+S(b-d));
}
vector<Data>arr;
double sum=0,ans=0x7fffffff;
void dfs(int k,int vis,int cnt,double sum)
{
	if(cnt==n)
	{
		ans=min(ans,sum);
		return ;
	}
	for(int i=vis;i;i-=(i&-i))
	{
		int j=ind[i&-i];
		double l=sum+DIS(arr[k].x,arr[k].y,arr[j].x,arr[j].y);
		if(dp[vis-(1<<j)][j]!=0&&dp[vis-(1<<j)][j]<=l)continue;
		dp[vis-(1<<j)][j]=l;
		dfs(j,vis-(1<<j),cnt+1,l);
	}
	return ;
}

int main()
{
	cin>>n;
	arr.push_back(Data(0,0));
	for(int i=1;i<=n;i++)ind[(1<<i)]=i;
	double a,b;
	for(int i=1;i<=n;i++)
	{
		scanf("%lf%lf",&a,&b);
		arr.push_back(Data(a,b));
	}
	dfs(0,MASK(n),0,0);
	printf("%.2lf",ans);
	return 0;
}

思路比较清晰,但是实现正确的难度比较大。

7.单词接龙

题目描述

单词接龙是一个与我们经常玩的成语接龙相类似的游戏,现在我们已知一组单词,且给定一个开头的字母,要求出以这个字母开头的最长的“龙”(每个单词都最多在“龙”中出现两次),在两个单词相连时,其重合部分合为一部分,例如 beastastonish,如果接成一条龙则变为 beastonish,另外相邻的两部分不能存在包含关系,例如 atatide 间不能相连。

输入格式

输入的第一行为一个单独的整数 n n n 表示单词数,以下 n n n 行每行有一个单词,输入的最后一行为一个单个字符,表示“龙”开头的字母。你可以假定以此字母开头的“龙”一定存在。

输出格式

只需输出以此字母开头的最长的“龙”的长度。

样例 #1

样例输入 #1

5
at
touch
cheat
choose
tact
a

样例输出 #1

23

提示

样例解释:连成的“龙”为 atoucheatactactouchoose

n ≤ 20 n \le 20 n20

代码实现

#include<iostream>
#include<string>
using namespace std;
#define MAXSIZE 20
string str[MAXSIZE+5],start;
int g[MAXSIZE+5][MAXSIZE+5];
int vis[MAXSIZE+5];
int ans=0;	
int n;
void dfs(int k,int len)
{
	ans=max(ans,len);
	vis[k]--;
	for(int i=1;i<=n;i++)
	{
		if(vis[i]&&g[k][i]!=0)
		{
			dfs(i,len+str[i].size()-g[k][i]);
		}
	}
	vis[k]++;
	return ;
}
int f(string a,string b)
{
	int flag;
	for(int i=a.size()-1;i>=1;i--)
	{
		flag=1;
		for(int j=0;j<a.size()-i;j++)
		{
			if(a[i+j]==b[j])continue;
			flag=0;
			break;
		}
		if(flag)return a.size()-i;
	}
	return 0;
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	cin>>str[i];
	cin>>start;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			g[i][j]=f(str[i],str[j]);
		}
	}
	for(int i=1;i<=n;i++)vis[i]=2;
	for(int i=1;i<=n;i++)
	{
		if(str[i][0]==start[0])
		dfs(i,str[i].size());
	}
	cout<<ans;
	return 0;
}

8.字串变换

题目背景

本题不保证存在靠谱的多项式复杂度的做法。测试数据非常的水,各种做法都可以通过,不代表算法正确。因此本题题目和数据仅供参考。

本题为搜索题,本题不接受 hack 数据。关于此类题目的详细内容

题目描述

已知有两个字串 A , B A,B A,B 及一组字串变换的规则(至多 6 6 6 个规则),形如:

  • A 1 → B 1 A_1\to B_1 A1B1
  • A 2 → B 2 A_2\to B_2 A2B2

规则的含义为:在 A A A 中的子串 A 1 A_1 A1 可以变换为 $ B_1 , , A_2$ 可以变换为 B 2 ⋯ B_2\cdots B2

例如: A = abcd A=\texttt{abcd} A=abcd B = xyz B=\texttt{xyz} Bxyz

变换规则为:

  • abc → xu \texttt{abc}\rightarrow\texttt{xu} abcxu ud → y \texttt{ud}\rightarrow\texttt{y} udy y → yz \texttt{y}\rightarrow\texttt{yz} yyz

则此时, A A A 可以经过一系列的变换变为 B B B,其变换的过程为:

  • abcd → xud → xy → xyz \texttt{abcd}\rightarrow\texttt{xud}\rightarrow\texttt{xy}\rightarrow\texttt{xyz} abcdxudxyxyz

共进行了 3 3 3 次变换,使得 A A A 变换为 B B B

输入格式

第一行有两个字符串 A , B A,B A,B

接下来若干行,每行有两个字符串 A i , B i A_i,B_i Ai,Bi,表示一条变换规则。

输出格式

若在 10 10 10 步(包含 10 10 10 步)以内能将 A A A 变换为 B B B,则输出最少的变换步数;否则输出 NO ANSWER!

样例 #1

样例输入 #1

abcd xyz
abc xu
ud y
y yz

样例输出 #1

3

提示

对于 100 % 100\% 100% 数据,保证所有字符串长度的上限为 20 20 20

思路比较清晰,难点在于实现,用到了cstring库的一些函数。根据题目特点,这里是使用bfs来实现的。
代码实现

#include<iostream>
#include<string>
#include<vector>
#include<queue>
#include<cstring>
using namespace std;
#define MAX_N 20
int t=0;
struct Data{
	Data(string a,string b):from(a),to(b){}
	string from,to;
};
vector<Data>str; 
string replace(string word,string s1,string s2,int&pos)
{
	pos=word.find(s1,pos+1);
	if(pos!=-1)
	{
		word.erase(pos,s1.size());
		word.insert(pos,s2);
	}
	return word;
}
int flag=0;
int ans=0;
void bfs(string now,string item)
{
	queue<pair<string,int>>q;
	q.push(pair<string,int>(now,0));
	while(!q.empty())
	{
		string s=q.front().first;
		int cnt=q.front().second;
		q.pop();
		if(cnt>10)
		{
		return ;
		}
		if(s==item)
		{
		ans=cnt;
		flag=1;
		cout<<ans<<endl;
		return ;
		}
		
		for(int i=0;i<t;i++)
		{
			int pos=-1;
			do{
				string ss=replace(s,str[i].from,str[i].to,pos);
				if(pos!=-1)q.push(pair<string,int>(ss,cnt+1));
			}while(pos!=-1);
		}
	}
	return ;
}
int main()
{
	string A,B;
	cin>>A>>B;
	string a,b;
	while(cin>>a>>b)
	{
		str.push_back(Data(a,b));
		t++;	
	}
	bfs(A,B);
	if(flag==0)cout<<"NO ANSWER!"<<endl;
	return 0;
 } 

目前能够实现的版本,代码执行正确,但是最后一组数据超时超限。

9.自然数的拆分问题

题目描述

任何一个大于 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

代码实现

#include<iostream>
using namespace std;
int arr[10];
void dfs(int a,int cnt,int item,int sum)
{
	if(sum==item)
	{
		if(cnt!=2)
		for(int i=1;i<cnt;i++)
		{
			printf("%d",arr[i]);
			if(i!=cnt-1)printf("+");
		}
		cout<<endl;
		return ;
	}
	for(int i=a;i<=item-sum;i++)
	{
		arr[cnt]=i;
		dfs(i,cnt+1,item,sum+i);
	}
	return ;
}
int main()
{
	int n;
	cin>>n;
	dfs(1,1,n,0);
	return 0;
}

10.单词方阵

题目描述

给一 n × n n \times n n×n 的字母方阵,内可能蕴含多个 yizhong 单词。单词在方阵中是沿着同一方向连续摆放的。摆放可沿着 8 8 8 个方向的任一方向,同一单词摆放时不再改变方向,单词与单词之间可以交叉,因此有可能共用字母。输出时,将不是单词的字母用 * 代替,以突出显示单词。

输入格式

第一行输入一个数 n n n ( 7 ≤ n ≤ 100 ) (7 \le n \le 100) (7n100)

第二行开始输入 n × n n \times n n×n 的字母矩阵。

输出格式

突出显示单词的 n × n n \times n n×n 矩阵。

样例 #1

样例输入 #1

7
aaaaaaa
aaaaaaa
aaaaaaa
aaaaaaa
aaaaaaa
aaaaaaa
aaaaaaa

样例输出 #1

*******
*******
*******
*******
*******
*******
*******

样例 #2

样例输入 #2

8
qyizhong
gydthkjy
nwidghji
orbzsfgz
hhgrhwth
zzzzzozo
iwdfrgng
yyyygggg

样例输出 #2

*yizhong
gy******
n*i*****
o**z****
h***h***
z****o**
i*****n*
y******g

代码实现

在这里插入代码片#include<iostream>
using namespace std;
#define MAXSIZE 100
char c[MAXSIZE+5][MAXSIZE+5]={0};
int vis[MAXSIZE+5][MAXSIZE+5]={0};
int ans[MAXSIZE+5][MAXSIZE+5]={0};
int pos[8][2]={{0,1},{1,1},{1,0},{-1,0},
			{1,-1},{0,-1},{-1,-1},{-1,1}};
string item="yizhong";

void dfs(int i,int j)
{
	if(c[i][j]=='\0')return ;//记得加!
	if(vis[i][j]) return ;
	if(c[i][j]=='y')
	{
		for(int k=0;k<8;k++)
		{
			int flag=1;
			for(int kk=0;item[kk];kk++)
			{
				int x=i+kk*pos[k][0];
				int y=j+kk*pos[k][1];
				if(c[x][y]==item[kk])continue;
				flag=0;
				break;
			}
			if(flag)
			{
				for(int kk=0;item[kk];kk++)
				{
					int x=i+kk*pos[k][0];
					int y=j+kk*pos[k][1];
					ans[x][y]=1;
				}
			}
		}
	}
	vis[i][j]=1;
	dfs(i,j+1);
	dfs(i+1,j);
	return ;
}
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	cin>>(c[i]+1);
	dfs(1,1);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(ans[i][j]==0)cout<<"*";
			else cout<<c[i][j];
		}
		cout<<endl;
	}
	return 0;
}

其实可以直接遍历求解,不需要使用回溯。

11.考前临时抱佛脚

题目背景

kkksc03 的大学生活非常的颓废,平时根本不学习。但是,临近期末考试,他必须要开始抱佛脚,以求不挂科。

题目描述

这次期末考试,kkksc03 需要考 4 4 4 科。因此要开始刷习题集,每科都有一个习题集,分别有 s 1 , s 2 , s 3 , s 4 s_1,s_2,s_3,s_4 s1,s2,s3,s4 道题目,完成每道题目需要一些时间,可能不等( A 1 , A 2 , … , A s 1 A_1,A_2,\ldots,A_{s_1} A1,A2,,As1 B 1 , B 2 , … , B s 2 B_1,B_2,\ldots,B_{s_2} B1,B2,,Bs2 C 1 , C 2 , … , C s 3 C_1,C_2,\ldots,C_{s_3} C1,C2,,Cs3 D 1 , D 2 , … , D s 4 D_1,D_2,\ldots,D_{s_4} D1,D2,,Ds4)。

kkksc03 有一个能力,他的左右两个大脑可以同时计算 2 2 2 道不同的题目,但是仅限于同一科。因此,kkksc03 必须一科一科的复习。

由于 kkksc03 还急着去处理洛谷的 bug,因此他希望尽快把事情做完,所以他希望知道能够完成复习的最短时间。

输入格式

本题包含 5 5 5 行数据:第 1 1 1 行,为四个正整数 s 1 , s 2 , s 3 , s 4 s_1,s_2,s_3,s_4 s1,s2,s3,s4

2 2 2 行,为 A 1 , A 2 , … , A s 1 A_1,A_2,\ldots,A_{s_1} A1,A2,,As1 s 1 s_1 s1 个数,表示第一科习题集每道题目所消耗的时间。

3 3 3 行,为 B 1 , B 2 , … , B s 2 B_1,B_2,\ldots,B_{s_2} B1,B2,,Bs2 s 2 s_2 s2 个数。

4 4 4 行,为 C 1 , C 2 , … , C s 3 C_1,C_2,\ldots,C_{s_3} C1,C2,,Cs3 s 3 s_3 s3 个数。

5 5 5 行,为 D 1 , D 2 , … , D s 4 D_1,D_2,\ldots,D_{s_4} D1,D2,,Ds4 s 4 s_4 s4 个数,意思均同上。

输出格式

输出一行,为复习完毕最短时间。

样例 #1

样例输入 #1

1 2 1 3		
5
4 3
6
2 4 3

样例输出 #1

20

提示

1 ≤ s 1 , s 2 , s 3 , s 4 ≤ 20 1\leq s_1,s_2,s_3,s_4\leq 20 1s1,s2,s3,s420

1 ≤ A 1 , A 2 , … , A s 1 , B 1 , B 2 , … , B s 2 , C 1 , C 2 , … , C s 3 , D 1 , D 2 , … , D s 4 ≤ 60 1\leq A_1,A_2,\ldots,A_{s_1},B_1,B_2,\ldots,B_{s_2},C_1,C_2,\ldots,C_{s_3},D_1,D_2,\ldots,D_{s_4}\leq60 1A1,A2,,As1,B1,B2,,Bs2,C1,C2,,Cs3,D1,D2,,Ds460

代码实现

#include<iostream>
using namespace std;
#define MAX_S 20
int s[10];
int arr[10][MAX_S+5];
int ans;
void dfs(int x,int y,int l,int r)
{
	if(x==s[y])
	{
		ans=min(ans,max(l,r));
		return ;
	}
	dfs(x+1,y,l+arr[y][x],r);//把问题丢给左脑 
	dfs(x+1,y,l,r+arr[y][x]);//把问题丢给右脑 
	return ;
}
int main()
{
	for(int i=0;i<4;i++)
	cin>>s[i];
	for(int i=0;i<4;i++)
	for(int j=0;j<s[i];j++)
	cin>>arr[i][j];
	int sum=0;
	for(int i=0;i<4;i++)
	{
		ans=0x7fffffff;
		dfs(0,i,0,0);
		sum+=ans;
	}
	cout<<sum;
	return 0;
}
  • 27
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,关于 jQuery 的事件,我可以和你分享一些学习笔记。 1. 绑定事件 在 jQuery 中,我们可以通过以下方式来绑定事件: ``` $(selector).event(function(){ // 事件处理程序 }) ``` 其中,`selector` 表示要绑定事件的元素,`event` 表示要绑定的事件类型,比如 `click`、`mouseover` 等等。事件处理程序则是在事件触发时要执行的代码块。 2. 多个事件绑定 我们可以通过 `on()` 方法来同时绑定多个事件: ``` $(selector).on({ event1: function(){ // 事件处理程序1 }, event2: function(){ // 事件处理程序2 } }) ``` 这样,当 `event1` 或 `event2` 中任意一个事件触发时,对应的处理程序都会被执行。 3. 解除事件 如果需要解除某个元素的事件处理程序,可以使用 `off()` 方法: ``` $(selector).off(event); ``` 其中,`event` 表示要解除的事件类型。如果不指定事件类型,则会解除该元素上所有的事件处理程序。 4. 事件委托 在 jQuery 中,我们可以使用事件委托来提高性能。事件委托是指将事件绑定到父元素上,而不是绑定到子元素上,然后通过事件冒泡来判断是哪个子元素触发了该事件。这样,当子元素数量较多时,只需要绑定一次事件,就可以监听到所有子元素的事件。 ``` $(selector).on(event, childSelector, function(){ // 事件处理程序 }) ``` 其中,`selector` 表示父元素,`event` 表示要绑定的事件类型,`childSelector` 表示要委托的子元素的选择器,事件处理程序则是在子元素触发事件时要执行的代码块。 以上是 jQuery 中事件的一些基本操作,希望对你有所帮助。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值