深搜
不撞南墙不回头 \color{red}{不撞南墙不回头} 不撞南墙不回头
数据
<100, 否则bfs
DFS树
跳马求方案数(1644)
学会设置
偏移量
判越界,判重,回溯
中国象棋半张棋盘如图 1 1 1 所示。马自左下角 ( 0 , 0 ) (0,0) (0,0) 向右上角 ( m , n ) (m,n) (m,n) 跳。规定只能往右跳,不准往左跳。比如图 1 1 1 中所示为一种跳行路线,并将路径总数打印出来。
输入格式
只有一行:两个数
n
n
n,
m
m
m。
输出格式
只有一个数:总方案数
t
o
t
a
l
total
total。
样例输入
4 8
样例输出
37
该题无需判重和回溯
\color{red}{该题无需判重和回溯}
该题无需判重和回溯
迷宫求方案数(1605)
给定一个
N
×
M
N \times M
N×M 方格的迷宫,迷宫里有
T
T
T 处障碍,障碍处不可通过。
在迷宫中移动有上下左右四种方式,每次只能移动一个方格。数据保证起点上没有障碍。
给定起点坐标
数据范围
对于
100
%
100\%
100% 的数据,
1
≤
N
,
M
≤
5
1 \le N,M \le 5
1≤N,M≤5,
1
≤
T
≤
10
1 \le T \le 10
1≤T≤10,
1
≤
S
X
,
F
X
≤
n
1 \le SX,FX \le n
1≤SX,FX≤n,
1
≤
S
Y
,
F
Y
≤
m
1 \le SY,FY \le m
1≤SY,FY≤m。
输入格式
第一行为三个正整数
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 行,每行两个正整数,表示障碍点的坐标。
输出格式
输出从起点坐标到终点坐标的方案总数。和终点坐标,每个方格最多经过一次,问有多少种从起点坐标到终点坐标的方案。
输入
2 2 1
1 1 2 2
1 2
输出
1
棋盘问题
int n,m,res;
char g[N][N];
bool st[N];
void dfs(int x,int cnt){
if(cnt==m){
res++;
return;
}
if(x>=n) return;
for(int i=0;i<n;i++){
if(g[x][i]=='#' && !st[i]){
st[i]=1;
dfs(x+1,cnt+1);
st[i]=0;
}
}
dfs(x+1,cnt);
}
int main(){
while(cin>>n>>m, n>0 || m>0){
char g[N][N];
for(int i=0;i<n;i++) cin>>g[i];
// 为什么不能从1开始?
//虽然行是从1开售
//但列还是从0开始
res=0;
dfs(0,0);
cout<<res<<endl;
}
return 0;
}
八皇后(1219)
一定要注意挖掘对角线与坐标隐含的
映射关系
判越界,判重,回溯
从0
开始if(x==n);从1
开始if(x>n)
一个如下的
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 大小的。
【数据范围】
对于
100
%
100\%
100% 的数据,
6
≤
n
≤
13
6 \le n \le 13
6≤n≤13。
输出格式
前三行为前三个解,每个解的两个数字之间用一个空格隔开。第四行只有一个数字,表示解的总数。输入
6
输出
2 4 6 1 3 5
3 6 2 5 1 4
4 1 5 2 6 3
4
int n,q[N],p[N],pos[N],c[N],ans;
void print(){
if(ans<=3){
rep(i,1,n){
printf("%d ",pos[i]);
}
puts("");
}
}
void dfs(int i){
if(i>n){
ans++;
print();
return;
}
rep(j,1,n){
if(c[j] || q[i+j] || p[i-j+n]) continue;
pos[i]=j;//记录i行放在第j列
c[j]=q[i+j]=p[i-j+n]=1;
dfs(i+1);
c[j]=q[i+j]=p[i-j+n]=0;
}
}
int main(){
cin>>n;
dfs(1);//是1不是n
printf("%d",ans);
return 0;
}
选数
选数+判断素数(质数)
int n,m,res;
int a[N],q[N];
int prime(int x){
if(x==1) return 0;
rep(i,2,x/i)
if(x%i==0) return 0;
return 1;
}
void dfs(int x,int y){
if(x+n-y<m) return;
//!!!
if(x>m){
ll sum=0;
rep(i,1,m) sum+=a[i];
if(prime(sum)) res++;
return;
}
rep(i,y,n){
a[x]=q[i];//
dfs(x+1,i+1);
a[x]=0;
}
}
int main(){
cin>>n>>m;
rep(i,1,n) cin>>q[i];
dfs(1,1);
cout<<res;
return 0;
}
P2089 烤鸡 指数
int n,res;
int a[N],g[N][10+5];
//x:十种配料的location
//y:当前已选调料的总质量
void dfs(int x,int y){
if(y>n) return;
if(x>10){
if(y==n){
res++;
rep(i,1,10) g[res][i]=a[i];
}
return;
}
rep(i,1,3){
a[x]=i;
dfs(x+1,y+i);//!!!
}
}
int main(){
cin>>n;
dfs(1,0);
cout<<res<<endl;
rep(i,1,res){
rep(j,1,10)
cout<<g[i][j]<<' ';
puts("");
}
return 0;
}
P1088 火星人 全排列
int n,m;
int a[N];
int main(){
cin>>n>>m;
rep(i,1,n) cin>>a[i];
//next_permutation(a+1,a+1+n):执行数组的范围
//rep(i,1,m):
rep(i,1,m) next_permutation(a+1,a+1+n);
rep(i,1,n) cout<<a[i]<<' ';
return 0;
}
P1149 火柴棒等式 指数 + 预处理
int n,res;
int a[N];
//num[N]:其余为0,num[]:真就只有10个
int num[100010]={6,2,5,5,4,5,6,3,7,6};
// int cal(int x){
// if(num[x]) return num[x];
// else{
// int cnt=0;
// while(x){
// cnt+=num[x%10];
// x/=10;
// }
// return cnt;
// }
// }
//TLE就剪枝
void dfs(int x,int sum){
if(sum>n) return;
if(x>3){
if(a[1]+a[2]==a[3] && sum==n) res++;
// rep(i,1,3) cout<<a[i]<<' ';
// puts("");
return;
}
rep(i,0,1000){
a[x]=i;
dfs(x+1,sum+num[i]);
a[x]=0;
}
}
int main(){
cin>>n;
n-=4;
//记忆化搜索
rep(i,10,100010) num[i]=num[i%10]+num[i/10];
dfs(1,0);
cout<<res;
return 0;
}
P2036 PERKET 指数
int n,has;
int a[N],b[N],st[N];
int res=1e9;
void dfs(int x){
if(x>n){
has=0;//判断调料
int sum1=1,sum2=0;
rep(i,1,n){
if(st[i]==1){
has=1;
sum1*=a[i];
sum2+=b[i];
}
}
if(has) res=min(res,abs(sum1-sum2));
return;
}
st[x]=1;
dfs(x+1);
st[x]=0;
st[x]=2;
dfs(x+1);
st[x]=1;
}
int main(){
cin>>n;
rep(i,1,n) cin>>a[i]>>b[i];
dfs(1);
cout<<res;
return 0;
}
P1683 入门
int n,m,res;
char g[N][N];
bool st[N][N];
int dx[]={-1,0,1,0},dy[]={0,-1,0,1};
void dfs(int x,int y){
for(int i=0;i<4;i++){
int a=x+dx[i],b=y+dy[i];
if(x<0 || x>=n || y<0 || y>=m) continue;
if(g[a][b]!='.') continue;
if(st[a][b]) continue;
st[a][b]=1;
res++;
dfs(a,b);
}
}
int main(){
cin>>m>>n;
// trick
for(int i=0;i<n;i++) cin>>g[i];
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
if(g[i][j]=='@'){
st[i][j]=true;
dfs(i,j);
}
res++;
cout<<res;
return 0;
}
P1596 Lake Counting S
int n,m,res;
char g[N][N];
bool st[N][N];
int dx[]={1,1,1,0,0,-1,-1,-1};
int dy[]={-1,0,1,-1,1,1,0,-1};
void dfs(int x,int y){
for(int i=0;i<8;i++){
int a=x+dx[i],b=y+dy[i];
if(x<0 || x>=n || y<0 || y>=m) continue;
if(g[a][b]!='W') continue;
if(st[a][b]) continue;
st[a][b]=1;
dfs(a,b);
}
}
int main(){
cin>>n>>m;
for(int i=0;i<n;i++) cin>>g[i];
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
if(g[i][j]=='W' && !st[i][j]){
res++;
dfs(i,j);
}
cout<<res;
return 0;
}
acw1114.棋盘问题 排列
int n,m;
char g[N][N];
bool st[N];
int res;//方案数
void dfs(int x,int cnt){
if(cnt==m){
res++;
return;
}
if(x>=n) return;
//枚举列
for(int i=0;i<n;i++){
if(!st[i] && g[x][i]=='#'){
st[i]=1;
dfs(x+1,cnt+1);
st[i]=0;
}
}
dfs(x+1,cnt);
}
int main(){
while(cin>>n>>m,n>0 && m>0){
for(int i=0;i<n;i++) cin>>g[i];
res=0;
dfs(0,0);
cout<<res<<endl;
}
return 0;
}
P1025 数的划分 组合
int n,m;
int res;
void dfs(int x,int s,int sum){
if(x>m){
if(sum==n) res++;
return;
}
rep(i,s,n-sum){//剪枝
dfs(x+1,i,sum+i);
}
}
int main(){
cin>>n>>m;
dfs(1,1,0);
cout<<res;
return 0;
}
P1019 单词接龙 指数 + 预处理
int n,res;
string w[N];//
int used[N];//每个单词的使用次数
int g[N][N];//存第i个单词能否连接到第j个单词,就是重合的长度
void dfs(string dragon,int x){
res=max(res,(int)dragon.size());
used[x]++;
for(int i=0;i<n;i++)//这里还会出现一次
if(g[x][i] && used[i]<2)
dfs(dragon+w[i].substr(g[x][i]),i);
used[x]--;
}
int main(){
cin>>n;
for(int i=0;i<n;i++) cin>>w[i];
char s;
cin>>s;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++){
string a=w[i],b=w[j];
for(int k=1;k<min(a.size(),b.size());k++){
if(a.substr(a.size()-k,k)==b.substr(0,k)){
g[i][j]=k;
break;
}
}
}
for(int i=0;i<n;i++)
if(w[i][0]==s)
dfs(w[i],i);
cout<<res;
return 0;
}
宽搜
一石惊起千层浪 \color{red}{一石惊起千层浪} 一石惊起千层浪
如果强行在dfs
存在最优解的情况使用bfs
, 容易MLE
BFS用到队列所具有的性质:单调性和二段性
经典BFS-马的遍历
数组模拟队列 \color{RED}{数组模拟队列} 数组模拟队列
若队不空,则队头永远在队尾的左边
int n,m,x,y;
int d[N][N];
// 坐标对齐必须严谨
int dx[]={2,2,1,1,-1,-1,-2,-2},dy[]={1,-1,2,-2,2,-2,1,-1};
PII q[N*N];// 矩阵面积大小
void bfs(){
ms(d,-1);
d[x][y]=0;
q[0]={x,y};
int hh=0,tt=0;
while(hh<=tt){
auto t = q[hh++];
for(int i=0;i<8;i++){
int a=t.fi+dx[i],b=t.se+dy[i];
if(a<1 || a>n || b<1 || b>m) continue;
if(d[a][b]>=0) continue;
d[a][b]=d[t.fi][t.se]+1;
q[++tt]={a,b};
}
}
}
int main(){
cin>>n>>m>>x>>y;
bfs();
rep(i,1,n){
rep(j,1,m)
printf("%-5d",d[i][j]);
puts("");
}
return 0;
}
好奇怪的游戏
每个起点有每个起点的目标,类似于单人模式
起点入队为q[0]={x,y}
int dx[]={1,2,2,1,-1,-2,-2,-1,2,2,-2,-2},dy[]={2,1,-1,-2,2,1,-1,-2,2,-2,2,-2};
int d[N][N];
PII q[N*N];
int x,y;
int bfs(){
ms(d,-1);
ms(q,0);
q[0]={x,y};
d[x][y]=0;
// st[x][y]=1;
int hh=0,tt=0;
while(hh<=tt){
auto t=q[hh++];
// if(x==1 && y==1) return d[x][y];
for(int i=0;i<12;i++){
int a=t.fi+dx[i],b=t.sc+dy[i];
if(a<=0 || a>100 || b<=0 || b>100) continue;
if(d[a][b]>=0) continue;
// if(st[a][b]) continue;
d[a][b]=d[t.fi][t.sc]+1;
if(a==1 && b==1) return d[a][b];
// cout<<d[a][b]<<endl;
// st[a][b]=1;
q[++tt]={a,b};
}
}
return -1;
}
int main(){
rep(i,1,2){
cin>>x>>y;
cout<<bfs()<<endl;
}
return 0;
}
多源BFS
多个起点
同时搜,最先到达的点的距离便是最短路
多个起点只要找到目标就返回,类似于团队游戏
起点入队为q[++tt]={x,y}
//多源BFS
int n,m,a,b;
int d[N][N];
PII q[N*N];
int hh=0,tt=-1;
int dx[]={-1,0,1,0},dy[]={0,-1,0,1};
void bfs(){
while(hh<=tt){
auto t=q[hh++];
for(int i=0;i<4;i++){
int a=t.fi+dx[i],b=t.se+dy[i];
if(a<1 || a>n || b<1 || b>m) continue;
if(d[a][b]>=0) continue;
d[a][b]=d[t.fi][t.se]+1;
q[++tt]={a,b};
}
}
}
int main(){
ms(d,-1);
cin>>n>>m>>a>>b;
while(a--){
int x,y;
cin>>x>>y;
q[++tt]={x,y};
d[x][y]=0;
}
bfs();
while(b--){
int x,y;
cin>>x>>y;
cout<<d[x][y]<<endl;
}
return 0;
}
双向BFS
如果知道起点和终点的状态,便可以降低时间和空间