本关任务:掌握回溯法算法思想,并能利用回溯法算法思想解决n位逐位整除数问题。
n位逐位整除数(简称整除数):从其高位开始,高1位能被整数1整除(显然),高2位能被整数2整除,…,整个n位能被整数n整除。给定整数n,求所有的n位整除数的个数。
例如,整数102450就是一个6位整除数。
为了完成本关任务,你需要掌握:1.回溯法的基本思想,2.回溯法的基本步骤,3.回溯法的算法框架,4.整除数的求解思路。
有许多问题,当需要找出它的解集或者要求回答什么解是满足某些约束条件的最佳解时,往往要使用回溯法。
回溯法的基本做法是搜索,或是一种组织得井井有条的,能避免不必要搜索的穷举式搜索法。这种方法适用于解一些组合数相当大的问题。
回溯法在问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树。算法搜索至解空间树的任意一点时,先判断该结点是否包含问题的解。如果包含,进入该子树,继续按深度优先策略搜索。如果肯定不包含,则跳过对该结点为根的子树的搜索,逐层向其祖先结点回溯。
根据回溯法的基本思想,可以得到回溯法的基本步骤如下:
其中,两个常用的剪枝函数为:
对于问题的一个实例,解向量满足显式约束条件的所有多元组,构成了该实例的一个解空间:
回溯法对解空间树作深度优先搜索,因此,在一般情况下用递归方法实现回溯法。具体算法框架及其伪代码如下:
void backt\frack (int t)//按深度优先从t层推进到第t+1层
{
if (t > n){
output (x); // 到达叶子结点,将计算或结果输出
}
else {
for (int i = f(n,t); i <= g(n,t); i ++ ){// 遍历结点t的所有子结点
x[t] = h[i];
if (constraint (t) && bound (t)){
backt\frack (t + 1);//如果不满足剪枝条件继续遍历
}
//回溯到该层的其他结点,然后继续搜索解空间
}
}
}
n位整除数的解法可以暴力枚举每一位数字,然后通过试除法进行验证,显然,这样的方法复杂度非常高,而且非常多的无效组合。运用回溯法算法可以很好的避免无效的组合,提高检索效率。根据回溯法的基本步骤,可得到如下回溯解法:
例如n=6时,回溯法的过程如下图所示。t=1,候选数字为0,1,2,…9,其中0是不符合整数第1位不为0的规则,设置当前的数字为a[t=1]=1;递归,t=t+1=2,数字0,2,4,6,8与a[1]所组合的整数a[1,2]是能被t=2整除的,设置当前的数字为a[t=2]=0;以此类推,直到t=7时,超过了指定的长度n=6,当前递归搜索结束,整除数个数加1。然后回溯到t=6时其它的合法数字,继续递归搜索。再然后回溯到t=5时其他的合法数字,继续递归搜索。以此类推,完成整个解空间的搜索。
本关的编程任务是补全右侧代码片段backt\frack
中Begin
至End
中间的代码,具体要求如下:
backt\frack
中,根据回溯法算法的思想,遍历和统计n
位逐位整除数的个数。 其中,参数数组a
记录满足条件的整除数(整除数长度n
超过int64
位存储范围,所以用整型数组模拟存储),参数t
(初始为1
)表示当前整除数长度(t<=n
),参数n
表示待查询的整除数长度,取地址引用的参数sum
表示整除数的个数。平台将自动编译补全后的代码,并生成若干组测试数据,接着根据程序的输出判断程序是否正确。
以下是平台的测试样例:
测试输入:6
预期输出:1200
思路:这里采用的是大数的运算,单纯的用int值会超界。
n位整除数的解释:例如例题给的整数102450是一个6位整除数。怎么是6位呢?1能被1整除,10能被2整除,102能被3整除以此类推,102450也正好能被6整除。也就是说n位整除数其前k位的高k位数都能被k整除。
那么我们就可以有剪枝操作,以2为例,10可以,11不可以被2整除,12可以,13不可以。那么我们就可以直接减掉11,13,也就是不会出现11和13开头的数字.
不考虑大数,以整型运算进行思考。int a,a表示当前存放的数,当前的整除数为t,如果a先更新:a=a*10+i;i的范围为0-9(t>1),如果a%t==0,那么就进行回溯。当满足t>n时回溯结束。
#include "stdio.h"
int n,sum=0,a=0;
void backtrack(int t){
if(t>n){
sum++;
return ;
}
else
{
if(t==1){
for(int i=1;i<10;i++){//一个数字都能被1整除
a=a*10+i;
backtrack(t+1);
a=a/10;
}
}
else{
for(int i=0;i<10;i++){
a=a*10+i;//更新a的值
if(a%t!=0){
a=a/10;
continue;
}
backtrack(t+1);
a=a/10;//a变回原来的数
}
}
return ;
}
}
int main(int argc, char const *argv[])
{
scanf("%d",&n);
backtrack(1);
printf("%d",sum);
return 0;
}
考虑大数,用数组来存放
//
// main.cpp
// step1
//
// Created by ljpc on 2018/12/8.
// Copyright © 2018年 ljpc. All rights reserved.
//
#include
#include
#include
using namespace std;
void backtrack(int *a, int t, int n, int &sum)
{
// 请在这里补充代码,完成本关任务
/********* Begin *********/
int r=0;//这里定义一个r
for(int i=1;i<t;i++){//r来进行计算除法
r=r*10+a[i];//向我们学过的除法计算一样,从第一位数字开始往后累加进行除法运算
r=r%(t-1);//r=0表示能够整除,这里a存放的数是t-1层的数,所以要和t-1进行整除
}
if(t==1){//第一层比较特殊,第一层表示是只有一个数,只能是个位1-9
for(int i=1;i<10;i++){
a[t]=i;
backtrack(a,t+1,n,sum);
a[t]=0;
}
}
else{
if(r!=0){//没能整除,剪枝操作
return ;
}
if(t>n){//到达临界值
if(r==0){
sum++;
}
return ;
}
if(t>1){
for(int i=0;i<10;i++){//1层之后的数字为0-9,例如10,200,3000可以包括0
a[t]=i;
backtrack(a,t+1,n,sum);
a[t]=0;
}
}
}
/********* End *********/
}
int main(int argc, const char * argv[]) {
int a[101];
int n;
scanf("%d", &n);
int sum = 0;
backtrack(a, 1, n, sum);
printf("%d\n", sum);
return 0;
}
本关任务:在n×n
格的棋盘上放置彼此不受攻击的 n 个皇后。按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。用非递归算法解决该问题。
下图是一个 8 个皇后的例子,8 个皇后彼此不受攻击。
请在右侧编辑器Begin-End
处补充代码,完成本关任务。
平台会对你编写的代码进行测试,比对你输出的数值与实际正确数值,只有所有数据全部计算正确才能通过测试:
测试输入:4
(皇后的数目)
预期输出:
* Q * *
* * * Q
Q * * *
* * Q *
* * Q *
Q * * *
* * * Q
* Q * *
4皇后问题共有2种摆放方案
开始你的任务吧,祝你成功!
思路:遍历n行皇后,第i行用数组x[i]来表示列数,如果满足皇后条件(见上一篇文章),那么久把行数增加,同时x[n]初始为0,每次一进入循环就增加x[i]的值,依次遍历列数。从1-n来存放数据。如果遍历完当前行所有的列,那就退出遍历列的循环,并把当前列x[i]=0,回退到上一行继续遍历行。当回退的t=0时所有数据遍历完。
#include
#include
int x[100]={0};//从1-n里放皇后,x[i]=1表示在那里放皇后
int n;
int judge(int k){
int y,z;
for(int i=1;i<k;i++){
y=i-k;
z=x[i]-x[k];
if(y<0) y=-y;
if(z<0) z=-z;
if(y==z||x[i]==x[k]) return 0;
}
return 1;
}
int main(int argc, char const *argv[])
{
scanf("%d",&n);
int t=1,sum=0;
while (t>0)//t小于1时结束
{
while(x[t]<n){//t从1到n放皇后
x[t]++;//在第t行开始放皇后,x[t]的范围为0-n,0表示没有放,1-n表示放置的列数,初始为0
if(t==n&&judge(t)){//在t行放置过皇后之后进行判断,如果满足放置到了n行并且位置无误
sum++;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(x[i]==j) printf(" Q");
else printf(" *");
}
printf("\n");
}
printf("\n");
}
else if(judge(t)){//部分解,进入下一行
t++;
}
}
//回到上一个点,并把当前t行的列位置置为0
x[t]=0;
t--;
}
printf("%d皇后问题共有%d种摆放方案",n,sum);
return 0;
}
本关任务:在n×n格的棋盘上放置彼此不受攻击的n个皇后。按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。用递归算法解决该问题。
请在右侧编辑器Begin-End
处补充代码,完成本关任务。
平台会对你编写的代码进行测试,比对你输出的数值与实际正确数值,只有所有数据全部计算正确才能通过测试:
测试输入:4
(皇后的数目)
预期输出:
* Q * *
* * * Q
Q * * *
* * Q *
* * Q *
Q * * *
* * * Q
* Q * *
4皇后问题共有2种摆放方案
开始你的任务吧,祝你成功!
#include
#include
int x[100]={0};//从1-n里放皇后,x[i]=1表示在那里放皇后
int n;
int sum=0;
int judge(int k){
int y,z;
for(int i=1;i<k;i++){
y=i-k;
z=x[i]-x[k];
if(y<0) y=-y;
if(z<0) z=-z;
if(y==z||x[i]==x[k]) return 0;
}
return 1;
}
void backtrack(int t){
while (t>0)//t小于1时结束
{
while(x[t]<n){//t从1到n放皇后
x[t]++;//在第t行开始放皇后,x[t]的范围为0-n,0表示没有放,1-n表示放置的列数,初始为0
if(t==n&&judge(t)){//在t行放置过皇后之后进行判断,如果满足放置到了n行并且位置无误
sum++;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(x[i]==j) printf(" Q");
else printf(" *");
}
printf("\n");
}
printf("\n");
}
else if(judge(t)){//部分解,进入下一行
t++;
}
}
//回到上一个点,并把当前t行的列位置置为0
x[t]=0;
t--;
}
}
int main(int argc, char const *argv[])
{
scanf("%d",&n);
backtrack(1);
printf("%d后问题共有%d种摆放方案",n,sum);
return 0;
}
本关任务:把从 1 到 n 这 n 个数摆成一个环,要求相邻的两个数的和是一个素数。
请在右侧编辑器Begin-End
处补充代码,完成本关任务,输出格式请参考测试集。
平台会对你编写的代码进行测试,比对你输出的数值与实际正确数值,只有所有数据全部计算正确才能通过测试:
测试输入:20
预期输出:围成的圈是:1 2 3 4 7 6 5 8 9 10 13 16 15 14 17 20 11 12 19 18
开始你的任务吧,祝你成功!
思路:写一个素数判断方法。注意:素数圈中的数字不可重复出现,用flag标记重复出现的数字。
重点注意:回溯遍历的结果不止一个,我们只要一个,所以当出现第一个结果时退出。exit(0);
#include
#include
/********** Begin **********/
int n;
int x[100]={0};
int flag[100]={0};
int sushu(int k){//判断该数字是否为素数
for(int i=2;i<=(int)sqrt(k);i++)
{
if(k%i==0) return 0;
}
return 1;
}
void backtrack(int t){//t代表第t个数
if(t>n){//t为n+1,输出素数圈
if(sushu(x[n]+x[1])){
printf("围成的圈是:");
for(int i=1;i<n;i++) printf("%d ",x[i]);
printf("%d",x[n]);
exit(0);
}
return ;
}
else{
for(int i=2;i<=n;i++){
if(sushu(i+x[t-1])&&flag[i]==0){
flag[i]=1;
x[t]=i;
backtrack(t+1);
flag[i]=0;
}
}
}
return ;
}
int main()
{
scanf("%d",&n);
x[1]=1;//环的第一个元素位置固定
flag[1]=1;
backtrack(2);
return 0;
}
/********** End **********/