蓝桥杯考前复习2
1.dfs深度优先搜索
a.简单的方向dfs搜索
解题思路:这道题目告诉你给定方向,固定往两个方向走,并给予一定的限定条件k,以及价值的判定,这就需要你对每个位置的价值进行判定,再决定要不要取这件宝物,在价值高于之前的前提下取与不取与需要分开判定,分开dfs。当到达最后的终点位置时,有两种情况,第一是已经取够了k件宝物,不需要取了,则答案直接加1后取模结束一条dfs,若没取够,则需要判定最后一件宝物是否价值大于取的前一件,满足条件则答案加1,则满足k,同上。
具体模板如下:
dfs(x,y,...){ if(满足结束的条件) {...,return; /*dfs()...*/}
if(满足某种变化情况){ dfs(); dfs(); dfs(); ... }
int main(){
dfs(0,0,...);
}
[蓝桥杯][2014年第五届真题]地宫取宝
题目描述:
X 国王有一个地宫宝库。是 n x m 个格子的矩阵。每个格子放一件宝贝。每个宝贝贴着价值标签。
地宫的入口在左上角,出口在右下角。
小明被带到地宫的入口,国王要求他只能向右或向下行走。
走过某个格子时,如果那个格子中的宝贝价值比小明手中任意宝贝价值都大,小明就可以拿起它(当然,也可以不拿)。
当小明走到出口时,如果他手中的宝贝恰好是k件,则这些宝贝就可以送给小明。
请你帮小明算一算,在给定的局面下,他有多少种不同的行动方案能获得这k件宝贝。
输入
输入一行3个整数,用空格分开:n m k (1< =n,m< =50, 1< =k< =12)
接下来有 n 行数据,每行有 m 个整数 Ci (0< =Ci< =12)代表这个格子上的宝物的价值
输出
要求输出一个整数,表示正好取k个宝贝的行动方案数。该数字可能很大,输出它对 1000000007 取模的结果。
样例输入
2 3 2
1 2 3
2 1 5
样例输出
14
#include<bits/stdc++.h>
using namespace std;
long long res=0;
int m,n,k;
const int Mod=1000000007;
int mapc[50][50];
void dfs(int x,int y,int max,int cnt) { //temp>0&&temp<12,k>0&&k<12
int temp=mapc[x][y];
if(x==m||y==n||cnt>k) return; //边界只考虑右边和下边会不会出界
if(x==m-1&&y==n-1) {
if(temp>max&&cnt==k-1){
res++; //取最后一件刚好满足k
if(res>Mod) res=res%Mod;
return;
}
if(cnt==k) {
res++; //不取最后一件已经满足k
if(res>Mod) res=res%Mod;
return;
}
}
if(temp>max) {
dfs(x+1,y,temp,cnt+1);//把价值更大的宝物取下来
dfs(x,y+1,temp,cnt+1);
dfs(x+1,y,max,cnt);//遇到价值更大但是不取
dfs(x,y+1,max,cnt);
} else {
dfs(x+1,y,max,cnt);
dfs(x,y+1,max,cnt);
}
}
int main() {
cin>>m>>n>>k;
for(int i=0; i<m; i++) {
for(int j=0; j<n; j++) {
cin>>mapc[i][j];
}
}
dfs(0,0,-1,0);
cout<<res<<endl;
return 0;
}
这道题目简单的用dfs访问每一个路劲,加上限制条件,不需要回溯…
b.利用暴力枚举然后dfs判断是否为联通区域,或利用全排列函数后再判断是否联通
思路:
简化条件:
先12选5 -> 12选不同的5 -> 12选不同的5而且还要连通 -> 12选不同的5而且还要连通,不能重复出现,且只有一个连通块。
做法1:
五重for循环+连通判断+除以A(5,5)=120(因为五重for循环选出的5个,可以以不同排列组成120种)
代码1:
#include<bits/stdc++.h>
using namespace std;
int g[3][4];
int ans=0;
//向四个方向拓展,沿路标记.
void dfs(int g[][4],int i,int j){
g[i][j]=0;
if((j+1)<4&&g[i][j+1]==1) dfs(g,i,j+1); //右
if((j-1)>=0&&g[i][j-1]==1) dfs(g,i,j-1); //左
if((i+1)<3&&g[i+1][j]==1) dfs(g,i+1,j); //下
if((i-1)>=0&&g[i-1][j]==1) dfs(g,i-1,j); //上
}
//将排列好的一维的数根据题意所需要的图形行列大小进行转化为一个二维来表示,并将参数中的点做标记为1.
void one_to_two(int g[][4],int n){
if(n%4==0){
g[n/4-1][3]=1; //4/4,8/4,12/4都为4的倍数,所以在数组中长度要减一
}else{
g[n/4][n%4-1]=1; //n/4是在n%4!=0的情况下的行数 1/4=0,2/4,3/4为0行第一行,5/4=1,6/4=1,7/4=1,就不需要减一了
}
}
void check(int g[][4],int a,int b,int c,int d,int e){
int cnt=0;
one_to_two(g,a);
one_to_two(g,b);
one_to_two(g,c);
one_to_two(g,d);
one_to_two(g,e);
//利用cnt进行联通判断
for(int i=0;i<3;i++){
for(int j=0;j<4;j++){
if(g[i][j]==1){
dfs(g,i,j);
cnt++;
if(cnt>=2) goto here;
}
}
}
here:
if(cnt==1) ans++; //如果该区域联通,则在第一个为1位置的dfs后所有位置值都均为0,则则在其他位置开始dfs时就会就不会再cnt++
}
int main() {
int a,b,c,d,e;
for(a=1; a<=12; a++) {
for(b=1; b<=12; b++) {
if(b==a) continue;
for(c=1; c<=12; c++) {
if(c==b||c==a) continue;
for(d=1; d<=12; d++) {
if(d==a||d==b||d==c) continue;
for(e=1; e<=12; e++) { //如果是按a-e从小到大是不行的,因为会有往左拐弯的情况。
if(e==a||e==b||e==c||e==d) continue;
check(g,a,b,c,d,e);
}
}
}
}
} //所以只能保证每一项不相等.
cout<<ans/120<<endl;
return 0;
}
做法2:全排列函数:
思路:因为是填空题,所以无所谓,但是做法一明显重复了A(5,5)次的判断。所以我们可以用全排列解决这个问题。
注意点:我们用一个a[12]={0,0,0,0,0,0,0,1,1,1,1,1}。来进行全排列。所以这里不建议手写全排列,因为你按模板手写会出现很多重复,因为他把每个0看做不一样的1也是。所以我们用next_permutation()。虽然你手写然后加上一些剪枝判断也行,所用的时间较长。
修改代码2(其余上同):
int a[12]={0,0,0,0,0,0,0,1,1,1,1,1} //均设置为全局变量
int main(){
do{
check();
}while(next_permutation(a,a+12));//全排列函数
cout<<ans;
return 0;
}