【C++】回溯算法基础入门

简介

  • 回溯算法基于深度优先搜索,实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。
  • 由于非递归式回溯算法较难实现,本文只介绍递归式回溯。

回溯算法框架

int a[n+1];//这里用下标1~n
void DFS(int i){//搜索第i层
	if(i>n){//搜索结束
		//结果处理(输出结果,方案计数等)
	}
	for(int j=下界;j<=上界;j++){
		if(限界函数&&约束条件){//满足限界函数和约束条件
			x[i]=j;//保存当前层的结果
			...//回溯入场准备工作
			DFS(i+1);//搜索下一层
			...//回溯退回清理工作
		}
	}
}

例题

打靶问题

题目描述

一个人打 10 10 10次靶(范围在 0 0 0环到 10 10 10环),问这 10 10 10次打靶之后,共中 90 90 90环的情况的个数。

输入格式

输出格式

输出中 90 90 90环的个数。

样例

参考题解

#include <iostream>
using namespace std;
int cnt=0;
void DFS(int k,int v){//k表示打第i次靶,v表示前i-1次靶得分 
    if(k==10){
        if(v+10>=90)//第10环满分,总分大于等于90说明有解 
            cnt++;//结果+1 
        return;
    }
    for(int i=0;i<=10;i++){//枚举第i次打靶得分 
    	if(v+i+10*(10-k)>=90){//如果后面几次都得满分达到90分才有解 
    		DFS(k+1,v+i);//搜索k+1层,更新得分v+i 
		}
    }
}
int main(){
    DFS(1,0);
    cout<<cnt;//输出方案数 
    return 0;
}

全排列1

题目描述

n n n个不同元素中任取 m ( m ≤ n ) m(m≤n) m(mn)个元素,按照一定的顺序排列起来,叫做从 n n n个不同元素中取出 m m m个元素的一个排列。

输入格式

一行一个正整数 n n n.

输出格式

输出所有排列,每个数字之间以空格隔开,每个结果之间以换行隔开。

样例输入

3

样例输出

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

数据范围与提示

1 ≤ n ≤ 7 1\le n \le 7 1n7

参考题解

#include <iostream>
using namespace std;
int x[8],n;
void DFS(int k){//k表示第k个数的搜索 
    if(k>n){//搜索结束,输出x[]数组 
        for(int i=1;i<=n;i++){
        	printf("%d ",x[i]);
		}
		printf("\n");
		return;
    }
    for(int i=1;i<=n;i++){//枚举x[k] 
    	x[k]=i;//标记x[k]=i 
    	DFS(k+1);//搜索下一层 
	}
}
int main(){
	cin>>n;
    DFS(1);
    return 0;
}

全排列2

题目描述

1 ∼ n 1\sim n 1n这 n 个整数排成一行后随机打乱顺序,输出所有可能的次序。

输入格式

一个整数 n n n

输出格式

按照从小到大的顺序输出所有方案,每行 1 1 1个。
首先,同一行相邻两个数用一个空格隔开。
其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面。

输入样例

3

输出样例

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

数据范围与提示

1 ≤ n ≤ 9 1\le n \le 9 1n9

参考题解

#include <iostream>
#include <algorithm>
using namespace std;
int x[10],visit[10],n;
void DFS(int k){//k表示第k个数的搜索 
    if(k>n){//搜索结束,输出x[]数组 
        for(int i=1;i<=n;i++){
        	printf("%d ",x[i]);
		}
		printf("\n");
		return;
    }
    for(int i=1;i<=n;i++){//枚举x[k]
    	if(!visit[i]){//如果i未被访问 
    		x[k]=i;//标记x[k]=i
    		visit[i]=1;//i标记1表示访问了 
    		DFS(k+1);//搜索下一层
    		visit[i]=0;//回溯回退,标记i为0表示访问 
		} 
	}
}
int main(){
	fill(visit,visit+10,0);//标记visit为0表示未访问 
	cin>>n;
    DFS(1);
    return 0;
}

组合的输出

题目描述

排列与组合是常用的数学方法,其中组合就是从 n n n个元素中抽出 r r r个元素(不分顺序且 r ≤ n r\le n rn),我们可以简单地将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,所有组合为:

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 <algorithm>
using namespace std;
int x[21],visit[21],n,r;
void DFS(int k){//k表示第k个数的搜索 
    if(k>r){//搜索结束,输出x[]数组 
        for(int i=1;i<=r;i++){
        	printf("%d ",x[i]);
		}
		printf("\n");
		return;
    }
    for(int i=x[k-1]+1;i<=n;i++){//枚举x[k],输出组合数不能有重复,当x[i]>x[i-1]时保证无重复
    	if(!visit[i]){//如果i未被访问 
    		x[k]=i;//标记x[k]=i
    		visit[i]=1;//i标记1表示访问了 
    		DFS(k+1);//搜索下一层
    		visit[i]=0;//回溯回退,标记i为0表示访问 
		} 
	}
}
int main(){
	x[0]=0;//为了方便下界使用,见DFS第二个for循环
	fill(visit,visit+21,0);//标记visit为0表示未访问 
	cin>>n>>r;
    DFS(1);
    return 0;
}

排列的生成

题目描述

给出 n n n m m m,请编程按字典序输从 1 , 2 , … , n 1,2,\dots ,n 1,2,,n中选择 m m m个数的所有排列。

输入格式

一行包含两个整数 n , m n,m n,m,两个整数之间用一个空格分开。

输出格式

按字典序输出所有可能的排列,每个排列输出一行,元素之间用一个空格分开。

样例输入

3 2

样例输出

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

数据范围与提示

1 ≤ m ≤ n ≤ 11 1\le m \le n \le 11 1mn11

#include <iostream>
#include <algorithm>
using namespace std;
int visit[12];//标记访问 
int num[12];//标记存储 
int n,m;//n的m排列 
void DFS(int k){
    if(k==m+1){//输出当前方案 
        for(int i=1;i<=m;i++){
            cout<<num[i]<<' ';
        }
        cout<<endl;
        return;
    }
    for(int i=1;i<=n;i++){//查找可选值 
        if(!visit[i]){//可选 
            visit[i]=1;//标记已选 
            num[k]=i;//记录已选 
            DFS(k+1);//搜索下一层 
            visit[i]=0;//回退,标记未选 
        }
    }
}
int main(){
    cin>>n>>m;
    fill(visit,visit+12,0);//访问标0 
    DFS(1);//调用回溯 
    return 0;
}

工作分配

题目描述

n n n项工作要分配给 m m m个人完成,每个人只能从事一项工作,且每项工作只能由一人完成。已知第 i i i个人完成第 j j j项工作的工费是 c [ i ] [ j ] c[i][j] c[i][j]元,那么怎么给每个人分配工作才能使得总工费最小。

输入格式

一个整数 n n n,接下来的 n n n行,每行一个 10000 10000 10000以内的正整数,其中第 i + 1 i+1 i+1行第 j j j列的整数 ,表示第 i i i个人完成第 j j j项工作时的工费。

输出格式

输出一个整数,表示最小的总工费。

样例输入

3
6 5 4
4 3 2
1 5 2

样例输出

8

数据范围与提示

2 ≤ n ≤ 20 2 \le n \le 20 2n20

#include <iostream>
#include <algorithm>
using namespace std;
int visit[21];//标记访问
int num[21][21];//工费矩阵
int preCost[21];//第i~n个员工的最小花费和 
int n;
int cost=1e9;//初始化最小花费为无限大
void DFS(int curCost,int index){//curCost,index分别表示index-1层花费和index层
    if(index>n){//递归边界 
        cost=min(cost,curCost);//更新花费 
        return;//回退 
    }
	for(int i=1;i<=n;i++){
        if(!visit[i]&&curCost+num[index][i]+preCost[index+1]<cost){//未访问,且最小花费小于当前值 
            visit[i]=1;
            DFS(curCost+num[index][i],index+1);//更新花费,搜索下一层 
            visit[i]=0;
        }
    }
}
int main(){
    scanf("%d",&n);//输入n 
    for(int i=1;i<=n;i++){
        visit[i]=0;//标记未访问 
        preCost[i]=1e9;
        for(int j=1;j<=n;j++){
        	scanf("%d",&num[i][j]);//输入花费
        	preCost[i]=min(preCost[i],num[i][j]);
		}
    }
    preCost[n+1]=0;//为了方便计算 
    for(int i=n-1;i>=1;i--) preCost[i]+=preCost[i+1];//计算i~n的最小花费 
    DFS(0,1);
    printf("%d",cost);//输出最小花费 
    return 0;
}

方格填数

题目描述

n ∗ n n*n nn的棋盘上,填入 1 , 2 , … , n ∗ n 1,2,\dots ,n*n 1,2,,nn n ∗ n n*n nn个数,使得任意两个相邻的数之和为素数。例如:
请添加图片描述

在这里我们约定:左上角的格子里必须填数字 1 1 1

输入格式

一行一个整数: n n n

输出格式

输出解时按行从上到下,每行从左到右依次输出。如有多种解,则输其中字典序由小到大的前三个解,若不足三个,则按字典序全部输出,如果无解,则输出 0 0 0

样例输入

4

样例输出

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

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

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

数据范围与提示

1 ≤ n ≤ 10 1\le n\le 10 1n10

#include <iostream>
#include <cstdio>
#include <algorithm>
#define N 11
using namespace std;
int mp[N][N];//方案值存取 
int visit[N*N];//访问标记 
int isPrime[101];//素数标记 
int n,k=0;//n表示n*n矩阵,k表示解的个数 
void init(){//素数打表
    fill(isPrime,isPrime+101,0);
    int num[25]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97};
    for(int i=0;i<25;i++) isPrime[num[i]]=1;
}
void show(){//输出矩阵 
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            printf("%d ",mp[i][j]);
        }
        printf("\n");
    }
    printf("\n");
    k++;//记录方案数+1 
}
int test(int x,int y,int i){//试探i能不能放在(x,y) 
//每一个需要满足左边和上边的加起来是素数 
    if(x>1&&isPrime[mp[x-1][y]+i]==0)
        return 0;
    if(y>1&&isPrime[mp[x][y-1]+i]==0)
        return 0;
    return 1;
}
void DFS(int x,int y){
    if(k==3)//找到3个解了,停止搜索 
        return;
    if(x==n+1){//搜索边界,满足要求 
        show();//输出矩阵 
        return;
    }
    if(x==1&&y==1){//(1,1)固定是1 
    	mp[x][y]=1;
    	DFS(1,2);
    	return; 
	}
    for(int i=2;i<=n*n;++i){//否则从2开始放置 
        if(!visit[i]){//未访问 
            mp[x][y]=i;//记录i 
            visit[i]=1;//标记访问 
            if(test(x,y,i)){//检测可否放置 
                if(y==n)//到了最后一列 
                    DFS(x+1,1);//搜索下一行第一列 
                else
                    DFS(x,y+1);//搜索本行下一列 
            }
            visit[i]=0;//回退标记未访问 
        }
    }
}
int main() {
    init();//生成素数表 
    cin>>n;
    k=0;
    mp[1][1]=1;//1号是1 
    fill(visit,visit+N*N,0);
    if(n!=1)//不等于1才有解 
        DFS(1,1);//从(1,1)开始搜索 
    if(k==0)//方案数为0 
        printf("0\n");
    return 0;
}

回文数(难题)

题目描述

“回文数” 则是有类似 22 , 383 , 5445 , 12321 22,383,5445,12321 22,383,5445,12321这些数,不论是从左向右顺读,还是从右向左倒读,结果都是一样的。
请编程找出区间 [ a , b ] [a,b] [a,b]范围内的所有回文数。

输入格式

1 1 1行一个整数 n n n,表示数据组数;
接下来的 n n n行,每行两个整数 a , b a,b a,b,表示区间 [ a , b ] [a,b] [a,b]

输出格式

输出 [ a , b ] [a,b] [a,b]区间内的回文数的数目。

输入样例

3
1 100
100 1000
873 1762810749

输出样例

18
90
117531
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
typedef long long ll;
string tmp;
string itos(ll n){//数字转字符串 
    string s;
    do{
        s=char(n%10+'0')+s;
        n/=10;
    }while(n);
    return s;
}
ll pow(ll a,ll b){//快速幂 
    if (b<0)
        return 0;
    ll sum=1;
    while(b){
        if(b&1)
            sum*=a;
        a*=a;
        b>>=1;
    }
    return sum;
}
ll len(ll n){//数的位数
    if(n==0)
        return 1;
    ll len=0;
    while(n){
        len++;
        n/=10;
    }
    return len;
}
ll calc(ll k){//计算k位回文数个数
    if(k==1)
        return 10;//1位全是 
    if(k==2)
        return 9;//2位的有11,22,...,99 
    if(k%2==1)
        return 9*pow(10,k/2);//奇数个,首尾9种,其余位10种 
    else
        return 9*pow(10,k/2-1);//偶数个,首尾9种,其余位10种 
}
void DFS(string b,ll r,ll k,ll &cnt){//计算小于等于b的l位回文数个数,l是b的长度,k是搜索第k层 
    if(k!=0&&tmp.substr(0,k)>b.substr(0,k))
        return;
    if(tmp.substr(0,k)<b.substr(0,k)){//临时数前缀比b小后面的位数不管了
        cnt+=pow(10,(r+1)/2-k);
        return;
    }
    if(k>=(r+1)/2){
        if(tmp<=b)
            cnt++;
        return;
    }
    for(ll i=0+(k==0);i<=9;i++){
        tmp[k]=tmp[r-1-k]=char('0'+i);
        DFS(b,r,k+1,cnt);
    }
}
ll solve(ll s) {//[0,s]回文数个数
    ll sum=0;
    for(int i=1;i<len(s);i++) sum+=calc(i);//小于s长度的回文串个数 
    tmp.resize(len(s));//改变长度 
	DFS(itos(s),len(s),0,sum);//搜索s等长回文串个数 
	return sum;
}
int main(){
    ll n,a,b;
    cin>>n;
    while(n--){
        cin>>a>>b;
        cout<<solve(b)-solve(a)<<endl;
    }
}

## 选数

题目描述

已知 n n n个整数,以及一个整数 m m m。从 n n n个整数中任选 m m m个整数相加,可分别得到一系列的和。例如当 n = 4 , m = 3 n=4,m=3 n=4,m=3,四个整数分别为 3 , 7 , 12 , 19 3,7,12,19 3,7,12,19时,可得全部的组合与它们的和为:
3 + 7 + 12 = 22 3 + 7 + 19 = 29 7 + 12 + 19 = 38 3 + 12 + 19 = 34 3+7+12=22\\ 3+7+19=29\\ 7+12+19=38\\ 3+12+19=34 3+7+12=223+7+19=297+12+19=383+12+19=34
现在,要求你计算出和为素数共有多少种。例如上例,只有一种的和为素数:
3 + 7 + 19 = 29 3+7+19=29 3+7+19=29

输入格式

1 1 1行包含两个正整数 n n n m m m
2 2 2行有 n n n个正整数: x 1 , x 2 , … , x n x_1,x_2,\dots ,x_n x1,x2,,xn

输出格式

一个整数,为答案。

输入样例

4 3
3 7 12 19

输出样例

1

数据范围与提示

1 ≤ k < n ≤ 25 1 ≤ x i ≤ 500000 1\le k < n \le 25\\ 1\le x_i \le 500000 1k<n251xi500000

#include <iostream>
#include <cstdio>
#include <algorithm>
#define N 26
using namespace std;
int x[N];
int mp[N];
int visit[N];
int n,m,total=0;
int isPrime(int k){//试探法求素数,优化效率可采用埃氏筛法或欧拉筛法 
    if(k==2)
        return 1;
    if(k<=1||k%2==0)
        return 0;
    for(int i=3;i*i<=k;i+=2){
        if(k%i==0)
            return 0;
    }
    return 1;
}
void solve(int k,int sum){//搜索第k层,sum是k-1层的和 
    if(k==m+1){//递归尽头 
        if(isPrime(sum))//是素数 
            total++;
        return;
    }
    for(int i=mp[k-1]+1;i<=n;i++){//因为是组合,所以要从上个数+1开始找,避免重复 
        if(!visit[i]){
            visit[i]=1;
            mp[k]=i;
            solve(k+1,sum+x[i]);//更新和,搜索下一层 
            visit[i]=0;
        }
    }
}
int main(){
    mp[0]=0;
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>x[i];
    solve(1,0);
    cout<<total;
    return 0;
}

0/1背包问题

题目描述

n n n种物品,每种物品只有 1 1 1个。第 i i i种物品的体积为 v [ i ] v[i] v[i],价值为 p [ i ] p[i] p[i]。选一些物品装入一个容量为 C C C的背包,使得背包内物品在总体积不超过 C C C的前提下价值尽量大。

输入格式

1 1 1行包含两个正整数 C C C n n n;接下来的 n n n行,每行两个整数: v [ i ] v[i] v[i] p [ i ] p[i] p[i],表示第 i i i种物品的体积和价值。

输出格式

一个整数,为答案。

输入样例

100 5
77 92
12 12
29 87
50 46
99 90

输出样例

145

数据范围与提示

1 ≤ n ≤ 30 1 ≤ v [ i ] ≤ C ≤ 1 , 000 , 000 , 000 1 ≤ p [ i ] ≤ , 000 , 000 1\le n\le 30\\ 1\le v[i]\le C \le 1,000,000,000\\ 1\le p[i]\le ,000,000 1n301v[i]C1,000,000,0001p[i],000,000

#include <iostream>
#include <algorithm>
using namespace std;
int C,n;//最大容量和背包数 
int mCost=0;//最大价值 
int v[31],p[31];//体积和价值
int preCost[32];//计算i~n的价值和 
void DFS(int k,int vNow,int pNow){//选第k个,当前花费
    if(k==n+1){//回溯边界 
        mCost=max(mCost,pNow);
        return;
    }
    //预测一下preCost取最大时能否超过当前和,不能就不必搜索 
    if(vNow+v[k]<=C&&pNow+p[k]+preCost[k+1]>=mCost) DFS(k+1,vNow+v[k],pNow+p[k]);//装k号背包 
    if(vNow<=C&&pNow+preCost[k+1]>=mCost) DFS(k+1,vNow,pNow);//不装k号背包 
}
int main(){
    cin>>C>>n;
    for(int i=1;i<=n;i++){
        cin>>v[i]>>p[i];
        preCost[i]=p[i];
    }
    for(int i=n-1;i>=1;i--) preCost[i]+=preCost[i+1];//记录i~n的价值和 
    preCost[n+1]=0;//方便计算 
    DFS(1,0,0);
    cout<<mCost;
    return 0;
}

放棋子

题目描述

给出一个 n ∗ m n*m nm的棋盘,要在棋盘上放 c c c个棋子, 使得任意两个棋子不相邻(上下左右)。问有多少种方案。比如 2 ∗ 3 2*3 23的棋盘上放 2 2 2个棋子有如下 8 8 8种合法方案。
请添加图片描述

输入格式

一行包含三个整数 。

输出格式

一个整数,表示方案数。

输入样例

2 3 2

输出样例

8

数据范围与提示

0 < n , m ≤ 20 且 n ∗ m ≤ 50 0<n,m\le 20且n*m\le 50 0<n,m20nm50

#include <iostream>
#include <algorithm>
#define N 21
using namespace std;
bool mp[N][N];
int n,m,c;
int total=0;
int test(int x,int y){//测试x,y能否放棋子
    if(y>1&&mp[x][y-1]==1)
        return 0;  //上方有棋子
    if(x>1&&mp[x-1][y]==1)
        return 0;  //左方有棋子
    return 1;
}
void DFS(int x,int y,int sum){//从x,y开始,已经放了sum个棋子
    if(sum==c){//放满了 
        total++;
        return;
    }
    if(y==m+1){//下一列
        x++;
        y=1;
    }
    if(x>n||sum+1+((n*m-(x-1)*m-y)+1)/2<c)
        return;//超出范围或放不下 
    mp[x][y]=1;
    if(test(x,y)) DFS(x,y+1,sum+1);//放了搜索下一层 
    mp[x][y]=0; 
    DFS(x,y+1,sum);//不放搜索下一层
}
int main(){
    fill(mp[0],mp[0]+N*N,0);
    cin>>n>>m>>c;
    DFS(1,1,0);
    cout<<total;
    return 0;
}

数的划分

题目描述

将整数 n n n分成 k k k份,且每份不能为空,任意两份不能相同(不考虑顺序)。

例如: n = 7 , k = 3 n=7,k=3 n=7,k=3,这三种分法: 7 = 1 + 1 + 5 ; 7 = 1 + 5 + 1 ; 7 = 5 + 1 + 1 7=1+1+5; 7=1+5+1; 7=5+1+1 7=1+1+5;7=1+5+1;7=5+1+1被认为是相同的。

给出 n n n k k k,请编程输出前 100 100 100个不同的分法。

输入格式

一行包含两个整数: n , k n,k n,k

输出格式

输出前 100 100 100个的不同分解方法,格式见样例,按分解称的 k k k个数排列的字典序输出。最后一行输出方案总数

样例例入

7 3

样例输出

7=1+1+5
7=1+2+4
7=1+3+3
7=2+2+3
4

数据范围与提示

1 < n ≤ 200 1 ≤ k ≤ 8 1<n\le 200\\ 1\le k\le 8 1<n2001k8

#include <iostream>
#include <algorithm>
#define N 201
using namespace std;
int n,m;
int num[N];
int total=0;
void DFS(int remind,int k){//剩余数remind拆分,回溯层数为k
    if(k==m){
        num[k]=remind;//最后一个就是remind 
        if(total<100){//小于100就输出方案数
        	printf("%d=",n);
            for(int i=1;i<=m;i++) {
                printf("%d",num[i]);
                if(i!=m)
                    printf("+");
            }
            printf("\n");
        }
        total++;//方案数+1 
        return;
    }
    for(int i=num[k-1];(m-k)*i<=remind-i;i++){//避免重复,num[k]需要大于等于num[k-1] 
        num[k]=i;
        DFS(remind-i,k+1);
    }
}
int main(){
    scanf("%d%d",&n,&m);
    num[0]=1;
    DFS(n,1);
    printf("%d",total);
    return 0;
}

n皇后问题

题目描述

n ∗ n n*n nn格的国际象棋上摆放 n n n个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上。请编一个程序找出 n n n皇后的所有解。

输入格式

一行一个整数 n n n

输出格式

按题目所说的序列方法输出,解按字典顺序排列。请输出前 3 3 3个解(不足 3 3 3个就全部输出)。最后一行是解的总个数。

样例例入

4

样例输出

2 4 1 3
3 1 4 2
2

样例输出种序列 2   4   1   3 2\ 4\ 1\ 3 2 4 1 3,第 i i i个数表示在第 i i i行的相应位置有一个棋子。 这是 4 4 4皇后问题的一个解。

#include <iostream>
#include <algorithm>
#define N 100
using namespace std;
int num[N];
bool visit[N];
int L[N],R[N];  //左右斜对角线
int n;
int total=0;
bool test(int x,int y){//测试斜对角线
    int l=x-y+n,r=x+y;
    if(L[l]||R[r])
        return 0;
    visit[y]=1;
    L[l]=1;//放置后标记左斜对角线 
    R[r]=1;//放置后标记右斜对角线
    return 1;
}
void reset(int x,int y){//回退重置斜对角线 
    int l=x-y+n,r=x+y;
    visit[y]=0;
    L[l]=0;
    R[r]=0;
}
void DFS(int i){//放第i行
    if(i==n+1){
        if(total<3){
            for(int i=1;i<=n;i++) printf("%d ",num[i]);
            printf("\n");
        }
        total++;
    }
    for(int j=1;j<=n;j++){
        if(!visit[j]&&test(i,j)){//测试i行放到j列
        	visit[j]=1;
            num[i]=j;
            DFS(i+1);
            reset(i,j);//回退重置斜对角线
        }
    }
}
int main(){
    scanf("%d",&n);
    DFS(1);
    printf("%d",total);
    return 0;
}

迷宫问题

题目描述

一个网络迷宫有 m m m n n n列的单元格组成,每个单元格要么是空地(用 1 1 1表示),要么是障碍物(用 0 0 0表示)。在迷宫中行走时,每一步只能上下左右移动到相邻的格子中,且任何时候都不能在障碍格中,也不能走道迷宫之外。起点和终点保证是空地。
请你编程找到一条从起点到终点移动步数最少的路线。

输入格式

1 1 1行包含两个整数 m m m n n n,表示迷宫是 m m m n n n列。

接下来的 m m m行每行包含长度为 n n n 01 01 01串,

其中第 i + 1 i+1 i+1行的第 j j j个字符是’0’,表示迷宫的第 i i i行,第 j j j列是障碍物,是’1’表示迷宫的第 i i i行第 j j j列是空地。

最后一行包含四个整数: s x , s y , e x , e y sx,sy,ex,ey sx,sy,ex,ey ( s x , s y ) (sx,sy) (sx,sy)表示起点行号和列号, ( e x , e y ) (ex,ey) (ex,ey)表示终点的行号 和列号。

注意:迷宫的行号和列号编号都是从 1 1 1开始的。

输出格式

第一行一个整数,表示最少的移动步数,若无解,则输出"None."。

#include <iostream>
#include <algorithm>
#define N 26
using namespace std;
char graph[N][N];
bool visit[N][N];
int dist[2][4]={{-1,1,0,0},{0,0,-1,1}};//上下左右走法的加减值 
int m,n,ex,ey;
int mCost=626;//最少步数标记 
bool canVisit(int x,int y,int cost){//可以走 
	if(visit[x][y]) return 0;//未走过 
	if(x<1||y<1||x>m||y>n) return 0;//超出边界 
	if(graph[x][y]=='0') return 0;//墙 
	if(cost+abs(x-ex)+abs(y-ey)>mCost) return 0;//步数预测,走直线最短,如果可走 
	return 1;
}
void DFS(int x,int y,int cost){//当前在(x,y),到达(x,y)之前步数为cost 
    if(x==ex&&y==ey){//到了终点 
        mCost=min(cost,mCost);
        return;
    }
    visit[x][y]=1;
    for(int i=0;i<4;i++){//走上下左右 
    	int p=x+dist[0][i],q=y+dist[1][i];
    	if(canVisit(p,q,cost+1)){
    		DFS(p,q,cost+1);
		}
	}
    visit[x][y]=0;
}
int main(){
    int x,y;
    scanf("%d%d\n",&m,&n);
    for(int i=1;i<=m;++i){
        for(int j=1;j<=n;++j){
            scanf("%c",&graph[i][j]);
        }
        scanf("\n");
    }
    scanf("%d%d%d%d",&x,&y,&ex,&ey);
    DFS(x,y,0);
    if(mCost!=626)
    	printf("%d",mCost);
    else
    	printf("None.");
    return 0;
}

配对

题目描述
n n n个元素( n n n是偶数),依次编号为 1 , 2 , … , n 1,2,\dots ,n 1,2,,n,现在要把它们配成 n / 2 n/2 n/2对,其中第 i i i个元素与第 j j j个元素配对的代价是 a [ i ] [ j ] a[i][j] a[i][j](保证 a [ i ] [ j ] = a [ j ] [ i ] a[i][j]=a[j][i] a[i][j]=a[j][i],且 a [ i ] [ i ] = 0 a[i][i]=0 a[i][i]=0),那么所有元素配对的最小代价是多少?

输入格式

1 1 1行为 n n n,表示有 n n n个元素。

接下来的 n n n行,每行 n n n个整数,表示 n ∗ n n*n nn的矩阵 ,其中矩阵的第 i i i行第 j j j列的整数表示 a [ i ] [ j ] a[i][j] a[i][j]

输出格式

一个整数,表示最小代价。

输入样例

4
0 100 5 100
100 0 100 11
5 100 0 100
100 11 100 0

输出样例

16

数据范围与提示

2 ≤ n ≤ 18 2\le n\le 18 2n18

#include <iostream>
#include <algorithm>
#define N 20
#define INF 999999
using namespace std;
int n;
int costM[N][N];//匹配花费矩阵 
int visit[N];//访问向量 
int mCost=INF;//最小花费 
int preCost=INF;//costM最小值
void DFS(int pre,int k,int cost){//配对第k对,pre是第k-1对较小值,cost是前k-1对和
	int i;
	for(i=pre+1;i<n&&visit[i];i++);//从pre+1开始放置避免重复,找到一个未被访问的i 
	visit[i]=1;
	for(int j=i+1;j<=n;j++){
		//j从i+1开始,保证配对i<j避免重复 
        if(!visit[j]&&cost+costM[i][j]+(n/2-k)*preCost<mCost){
		//未访问j且最小花费小于当前花费 
            visit[j]=1;
            if(k!=n/2) DFS(i,k+1,cost+costM[i][j]);//更新花费,搜索下一层
            else mCost=min(mCost,cost+costM[i][j]);//配对结束
            visit[j]=0;
        }
	}
	visit[i]=0;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            scanf("%d",&costM[i][j]);//输入 
            if(i!=j) preCost=min(preCost,costM[i][j]);//记录costM[i][j]最小值 
        }
    }
    DFS(0,1,0);
    printf("%d",mCost);
    return 0;
}

版权声明

  • 本文档归cout0所有,仅供学习使用,未经允许,不得转载。
  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cout0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值