按照字典序输出自然数 1 到 n 所有不重复的排列,即 n 的全排列,要求所产生的任一数字序列中不允许出现重复的数字。
#include
using namespace std;
int a[10],v[10];
int n;
void dfs(int x){
if(x==n+1){
for(int i=1;i<=n;i++) printf("%5d",a[i]);
cout<<endl;
return;
}
for(int i=1;i<=n;i++){
if(!v[i]){
a[x]=i;
v[i]=1;
dfs(x+1);
v[i]=0;
}
}
}
int main()
{
cin>>n;
dfs(1);
}
指数型,对于每一个要素有选与不选两种,构成2^n
种状态。
描述
工作需要完成多个习题册,一项习题集由多个题目组成。如果可以同时做一项习题册两个题目,问做完最少时间是多少。
思路
对于一项习题册,枚举每一道题目,做还是不做,如果做的习题的累计加大于习题册的1/2,那么可能就是做完该习题册的答案,取最小的。
#include
using namespace std;
int a[5][22],len[5],minn,ans;
void dfs(int x,int sum,int index){
if(x==len[index]+1){
if(sum>=(a[index][0]+1)/2){
//printf("index=%d sum=%d\n",index,sum);
minn=min(sum,minn);
}
return;
}
dfs(x+1,sum,index);//不加入
dfs(x+1,sum+a[index][x],index);//加入
}
int main()
{
for(int i=1;i<=4;i++) cin>>len[i];
for(int i=1;i<=4;i++){
for(int j=1;j<=len[i];j++){
cin>>a[i][j];
a[i][0]+=a[i][j];
}
}
for(int i=1;i<=4;i++){
minn=a[i][0];
dfs(1,0,i);
//cout<
ans+=minn;
}
cout<<ans<<endl;
}
#include
using namespace std;
#define N 10
int n,a[N],cnt;
void dfs(int x,int sum){
if(x==n) return;
if(sum>=n){
if(sum==n){
for(int i=1;i<cnt;i++) cout<<a[i]<<'+';
cout<<a[cnt]<<endl;
}
return;
}
a[++cnt]=x;
dfs(x,sum+x);
cnt--;
dfs(x+1,sum);
}
int main()
{
cin>>n;
dfs(1,0);
}
题目描述
已知 n 个整数 x1,x2,x3……xn
,以及整数 k(k
组合型枚举C(n,k)
与指数型枚举2^n
实现相似,截取前k
个.
#include
using namespace std;
#define N 10
int n,m,res[N],a[N],cnt,ans;
bool check(int x){
for(int i=2;i<=sqrt(x);i++){
if(x%i==0) return false;
}
return true;
}
void dfs(int x,int num,int sum){//是否选数a[x],已经选的数目num,已选数的和sum
//cout<
if(x==n+1||num==m){//达到上限或者达到组合数目暂停
if(num==m){
if(check(sum)) ans++;
}
return;
}
//选
dfs(x+1,num+1,sum+a[x]);
//不选
dfs(x+1,num,sum);
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
dfs(1,0,0);
cout<<ans;
}
应用:走迷宫,图的遍历、树的前中后遍历、可达性统计等。
搜索的过程:判断、打标记、记录、回溯
如何打标记,如何下一步搜索。
题目描述
一个N × M 的由非负整数构成的数字矩阵,你需要在其中取出若干个数字,使得取出的任意两个数字不相邻(若一个数字在另外一个数字相邻8个格子中的一个即认为这两个数字相邻),求取出数字和最大是多少。
思路
遍历的方式从第一行第一列开始,从左向右,到最后一列时然后增加行。每遍历一个数,标记周围八个(记得回溯的时候取消标记)。重点是如何标记?标识仅用0,1是不足够的,因为如果当前回溯取消标记,但有可能,在其它取数的周围,所以使用累加标记。
#include
using namespace std;
#define N 10
int n,m,num,maxx,t;
int g[N][N],v[N][N];
//标记:n个其他块覆盖到该块赋值为n,如果撤销一块那么该值减一
//如果用传统 0,1无法做到
int dx[8]={-1,-1,-1,0,0,1,1,1},
dy[8]={-1,0,1,-1,1,-1,0,1};//方向
void dfs(int x,int y){
if(x>=n+1){
maxx=max(maxx,num);
return;
}
if(y>=m+1){
dfs(x+1,1);
return;
}
dfs(x,y+1);//不选时
if(!v[x][y]){//选时
v[x][y]++;
for(int i=0;i<8;i++) v[x+dx[i]][y+dy[i]]++;
num+=g[x][y];
dfs(x,y+1);
v[x][y]--;//回溯
for(int i=0;i<8;i++) v[x+dx[i]][y+dy[i]]--;
num-=g[x][y];
}
}
int main()
{
cin>>t;
while(t--){
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>g[i][j];
}
}
memset(v,0,sizeof(v));
num=0,maxx=0;
dfs(1,1);
cout<<maxx<<endl;
}
}
一个如下的 6×6 的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行、每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子。
求解出多少种方式?只需要按行号1~n的顺序输出列号。
思路:按照1~n
的全排列输出的列号,可以满足每行、每列有且仅有一个。
例如n=3
全排列 {2,1,3}
则三个皇后的位置 (1,2)、(2,1)、(3,3)
。
题目要求对角线上有且仅有一个,如何判别对角线上是否有皇后?对于同一个对角线上的元素
,
,左上到右下的对角线一定满足y2-x2+n == y1-x1+n
, 从右上到左下的对角线满足x1+y1 == x2+y2
。
#include
using namespace std;
#define N 30
int res[N];
int n,ans,tot;
int row[N*2],col[N*2],v[N];
//v数组代表 该列号是否被访问
//row代表斜\对角线方向, col 代表/对角线方向
void dfs(int x){//输出列号y1,y2,y2,坐标依次为(1,y1)、(2,y2)、(3,y3)
if(x==n+1){
tot++;
if(tot<=3){
for(int i=1;i<=n;i++) cout<<res[i]<<' ';
cout<<endl;
}
return;
}
for(int y=1;y<=n;y++){
if(!v[y]&&!row[y-x+n]&&!col[x+y]){
v[y]=1;
row[y-x+n]=1;
col[x+y]=1;
res[x]=y;
dfs(x+1);
v[y]=0;//回溯
row[y-x+n]=0;
col[x+y]=0;
}
}
}
int main()
{
cin>>n;
dfs(1);
cout<<tot<<endl;
}
题目描述
开局一张格子图,格子上可能是水洼W
或者旱地.
,如果水洼的周围八个点有水洼,则将他们是为一个整体(湖泊)。问有多少个湖泊?
题目思路
DFS搜索题模板,遍历每一个格子,如果该格子是水洼且没有被标记,答案数量增加1,从该格子X
遍历,将所有相连是水洼的格子Y
的标记(就是再遍历到Y
不再计数)。
好熟悉的一道题,第一次做这道题时应该是在大一,当时ACM选修课每周天进行比赛遇到这道题,当时学了算法也一知半解,不会用。记得当时还一个下午都在模拟这道题,WA了很多次。
#include
using namespace std;
#define N 110
int n,m,v[N][N],ans;
string s[N];
int dx[8]={-1,-1,-1,0,0,1,1,1},
dy[8]={-1,0,1,-1,1,-1,0,1};
bool check(int x,int y)
{
return x>=0&&x<n&&y>=0&&y<m;
}
void dfs(int x,int y)
{
//cout<
for(int i=0; i<8; i++)
{
int xx=x+dx[i],yy=y+dy[i];
if(check(xx,yy)&& !v[xx][yy]&&s[xx][yy]=='W')
{
v[xx][yy]=1;
dfs(xx,yy);
}
}
}
int main()
{
cin>>n>>m;
for(int i=0; i<n; i++)cin>>s[i];
for(int i=0; i<n; i++)
{
for(int j=0; j<m; j++)
{
if(s[i][j]=='W'&& !v[i][j]){
ans++;
dfs(i,j);
}
}
}
cout<<ans<<endl;
}
题目描述
由数字0组成的方阵中,有一任意形状闭合圈,闭合圈由数字1构成。现要求把闭合圈内的所有空间都填写成2.
涂色前… … … … … … … 涂色后
题目思路
类似于上道题目湖泊数量,遍历方法类似,不过这道题目只学要分辨那些在边缘上,那些被封闭。方阵四个边缘上遍历一遍,这样就能得到所有未封闭的方阵。
#include
using namespace std;
#define N 35
int n,m,v[N][N],ans;
int a[N][N];
int dx[4]={-1,0,0,1},
dy[4]={0,-1,1,1};//上左右下
bool check(int x,int y){
return x>0&&x<=n&&y>0&&y<=n;
}
void dfs(int x,int y){
for(int i=0; i<4; i++){
int xx=x+dx[i],yy=y+dy[i];
if(check(xx,yy)&& !v[xx][yy]&&a[xx][yy]==0){
v[xx][yy]=1;
dfs(xx,yy);
}
}
}
void search_(int x,int y){
if(!v[x][y]&&a[x][y]==0){
v[x][y]=1;
dfs(x,y);
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>a[i][j];
}
}
for(int i=0; i<n; i++){
search_(1,i);
search_(n,i);
search_(i,1);
search_(i,n);
}
//cout<
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(a[i][j]) cout<<1<<' ';
else if(!a[i][j]&&v[i][j]) cout<<0<<' ';
else cout<<2<<' ';
}
cout<<endl;
}
}
题目描述
给出字符串方阵,类似于五子棋,寻找横向、纵向、或斜线方向构成yizhong
六个字符串,其他字符输出为*
;
题目思路
这道题用模拟也可以做,用搜索的话可能写的比较少。
#include
using namespace std;
#define N 110
int n,m,v[N][N],ans;
string s[N];
int dx[8]={-1,-1,-1,0,0,1,1,1},
dy[8]={-1,0,1,-1,1,-1,0,1};
bool check(int x,int y){
return x>=0&&x<n&&y>=0&&y<n;
}
string s1="yizhong";
pair<int,int> q[10];//暂存坐标
void dfs(int x,int y,int p,int num){//x,y代表坐标,p代表搜索方向,num代表遍历过的数量
//cout<
if(num>=6){
for(int i=0;i<=num;i++) v[q[i].first][q[i].second]=1;
return;
}
int xx=x+dx[p],yy=y+dy[p];
if(check(xx,yy)&&s[xx][yy]==s1[num+1]){
q[num+1].first=xx,q[num+1].second=yy;
dfs(xx,yy,p,num+1);
}
}
int main()
{
cin>>n;
for(int i=0;i<n;i++) cin>>s[i];
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
if(s[i][j]=='y'){
for(int k=0;k<8;k++){
q[0].first=i,q[0].second=j;
dfs(i,j,k,0);
}
}
}
}
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
if(!v[i][j]) cout<<'*';
else cout<<s[i][j];
}
cout<<endl;
}
}
应用:计算最小步数、树的层次遍历、最短路、拓扑排序。
题目描述
有一个 n×m 的棋盘,在某个点 (x, y) 上有一个马,要求你计算出马到达棋盘上任意一个点最少要走几步。
思路
使用两个队列,一个用于记录坐标,一个记录步数。
#include
using namespace std;
int n,m,x,y,sx,sy,val;
queue<pair<int,int> >q;
queue<int>d;
int dis[410][410],v[410][410];
int dx[8]={-1,-2,-2,-1,1,2,2,1},
dy[8]={-2,-1,1,2,-2,-1,1,2};
bool check(int x,int y){
if(x<=0||x>n||y<=0||y>m) return false;
return true;
}
int main()
{
cin>>n>>m>>sx>>sy;
memset(dis,-1,sizeof(dis));
q.push(make_pair(sx,sy));
d.push(0);
v[sx][sy]=1;
while(!q.empty()){
x=q.front().first,y=q.front().second;
q.pop();
val=d.front();d.pop();
dis[x][y]=val;
for(int i=0;i<8;i++){
int xx=x+dx[i],yy=y+dy[i];
if(!v[xx][yy]&&check(xx,yy)){
q.push(make_pair(xx,yy));
d.push(val+1);
v[xx][yy]=1;
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
printf("%-5d",dis[i][j]);
}
cout<<'\n';
}
}
洛谷-奇怪的电梯
题目描述
电梯的第i
层只能上行a[i]
到i+a[i]
层,或者下行a[i]
到i-a[i]
。问从第A
层出发最少多少次操作(上行 或者 下行 为一次操作)能够到达B层。
思路
最少步数一般使用BFS就可以了,需要注意一下细节就行了。
#include
using namespace std;
#define N 210
int n,m,x,y,A,B;
int a[N],dis[N],v[N];
queue<pair<int,int> >q;
bool check(int x){
if(x<=0||x>n) return false;
return true;
}
int main()
{
cin>>n>>A>>B;
for(int i=1;i<=n;i++) cin>>a[i];
memset(dis,-1,sizeof(dis));
memset(v,0,sizeof(v));
v[A]=1;
dis[A]=0;
q.push(make_pair(0,A));
while(!q.empty()){
x=q.front().second,y=q.front().first;
q.pop();
//printf("->%d:%d\n",x,y);
//printf("x+a[x]=%d : v[x+a[x]]=%d\n",x+a[x],v[x+a[x]]);
//printf("x-a[x]=%d : v[x-a[x]]=%d\n",x-a[x],v[x-a[x]]);
if(check(x+a[x])&& !v[x+a[x]]){
v[x+a[x]]=1;
dis[x+a[x]]=y+1;
q.push(make_pair(y+1,x+a[x]));
}
if(check(x-a[x])&& !v[x-a[x]]){
v[x-a[x]]=1;
dis[x-a[x]]=y+1;
q.push(make_pair(y+1,x-a[x]));
}
}
cout<<dis[B]<<endl;
}
题目描述
流星雨席卷农场,流星雨会坠t
时刻落到一个格子(x,y)
,同时会将周围四个格子及坠落的格子烧焦,无法行走(t时刻及t时刻以后
)。主角从(0,0)
出发,经过没有被烧焦的格子,到达安全地方最少时间。
思路,题目问最少时间,基本可以确定是广搜BFS
题目,本题特点是要处理流星坠落的数据,某个格子是否不会受到流星侵袭,也不会被烧焦,或者在什么时刻被烧焦?在搜索的的过程中:
1.需要判断是否到达了安全格子,到达立刻结束返回步数;
2.某个格子是否能经过。
3.如果流星雨规模过于集中,主角无路可逃,即搜索结束也没能找到安全格子,输出-1
坑点:主角可以逃到格子之外,但x>=0&&y>=0
,是否判断(0,0)是安全的?或者开局成盒?
#include
using namespace std;
#define N 610
int dx[5]={0,-1,0,0,1},//中上左右下
dy[5]={0,0,-1,1,0};
int g[N][N],v[N][N];
int x,y,t,m;
queue<pair<int,int> >q;//保存坐标
queue<int>d;//保存步数
bool check(int x,int y){
if(x<0||y<0) return false;
return true;
}
int main()
{
memset(g,-1,sizeof(g));
cin>>m;
for(int i=1;i<=m;i++){
cin>>x>>y>>t;
for(int j=0;j<=4;j++){
int xx=x+dx[j],yy=y+dy[j];
if(check(xx,yy)&&(g[xx][yy]==-1||g[xx][yy]>t)){
g[xx][yy]=t;
}
}
}
if(g[0][0] == -1){
cout<<0<<endl;
return 0;
}else if(g[0][0]==0){
cout<<-1<<endl;
return 0;
}
q.push(make_pair(0,0));
d.push(0);
v[0][0]=1;
while(!q.empty()){
x=q.front().first,y=q.front().second;
q.pop();
t=d.front();
d.pop();
for(int i=1;i<=4;i++){
int xx=x+dx[i],yy=y+dy[i];
if(check(xx,yy)&& !v[xx][yy]){
if(g[xx][yy]>t+1){
v[xx][yy]=1;
q.push(make_pair(xx,yy));
d.push(t+1);
}else if(g[xx][yy]==-1){
cout<<t+1<<endl;
return 0;
}
}
}
}
cout<<-1<<endl;
}