蓝桥杯算法入门_07 (抽象DFS)

目录

抽象DFS

从n个数中选取k个数和为sum

n根长度不同的木棍拼出等边三角形

 N皇后问题 (N个皇后不能处在同一行或同一列或同一斜线上)

求一个n元高次方程的解

数独 (较难)

双色皇后

引爆炸弹  


抽象DFS

抽象dfs特点: dfs看起来是图上完成的搜索算法,但是dfs的过程,我们没有看到图的存在

搜索树:
每个点有两个状态 选和不选 ,每个状态的子树
 

从n个数中选取k个数和为sum

样例输入:
5 3 9
1 2 3 4 5

输出:
2

#include <iostream>
using namespace  std;

int n_01,k_01,sum_01,ans_01;
int a_01[40];
void dfs_01(int i,int cnt,int s) {  // 当前取第i个数  , cnt为取了几个数 ,s表示当前的和
    if(i == n_01) {  //每次搜索完判断
        if(cnt == k_01 && s == sum_01) { //选了k个数 ,且和为sum
            ans_01++;
        }
        return;  //减少时间复杂度
    }
    dfs_01(i + 1, cnt , s);
    dfs_01(i + 1, cnt + 1, s + a_01[i]);   //s + a[i]表示为加上此步的和 ,递归到最后一个数n,判断此参数是否为sum
}

void test_01() {
    //输入数据
    cin>> n_01 >> k_01 >> sum_01;
    for(int i = 0; i < n_01; i++) {
        cin >> a_01[i];
    }
    ans_01 = 0;
    dfs_01(0,0,0);
    cout<<ans_01<<endl;
    return;
}

/*  另一种做法  要除以阶乘

否则为12
*/

int n_02,k_02,ans_02,sum_02;
int a_02[110];
bool xuan_02[110];  //标记是否选择了
void dfs_02(int s,int cnt) {
    if(s == sum_02 && cnt == k_02) {
        ans_02++;
    }
    for(int i = 0; i < n_02; i++) {
        if(!xuan_02[i]) {
            xuan_02[i] = 1;
            dfs_02(s + a_02[i],cnt + 1);
            xuan_02[i] = 0;
        }
    }
}

void test_02() {
    //输入数据
    cin>> n_02 >> k_02 >> sum_02;
    for(int i = 0; i < n_02; i++) {
        cin >> a_02[i];
    }
    ans_02 = 0;
    dfs_02(0,0);
    cout<< ans_02 << endl;
    return;

}

n根长度不同的木棍拼出等边三角形

输入n根木棍数 [3,10]
接下来一行输入每根木棍的长度  [1,10000]
每根木棍都要用到!
问是否能拼出

样例输入:
5
1 2 3 4 5

输出:
yes

#include<cstdio>
int p_03[15]; //木棍长度
int n_03,sum_03 = 0;
bool f_03;
bool vis[15];//标记木棍使用情况
void dfs_03(int p, int s, int st) { //当前拼成了p根边长    当前正在拼的木棍长度的和s   ,选择从哪开始选木棍st , 有多种重复排列解
    if(f_03) {     //找到就返回
        return;
    }
    if(p == 3) {    //找到三个 木棍
        f_03 = true;
        return;
    }
    if(s == sum_03 / 3) { //和为sum_03 / 3 已经拼成一条边长了,考虑下一根
        dfs_03(p + 1,0,0);//归为一个新的木棍  ,  再从 p + 1  开始拼
        return;
    }
    for(int i = 0; i < n_03; i++) {
        if( !vis[i]) { //从没用过木棍开始遍历
            vis[i] = true;//标记用过的木棍
            dfs_03(p, s + p_03[i] , i + 1);    //从第i根开始遍历
            vis[i] = false;
        }
    }
}
//蓝桥杯不用文件时输入输出 !
// freoprnen("triangle.in","r",stdin);
// freoprnen("triangle.out","w",stdout);

void test_03() {
    scanf("%d",&n_03);
    for(int i = 0 ; i < n_03; i++) {
        scanf("%d",&p_03[i]);//存入木棍长度
        sum_03 += p_03[i];        //求木棍总长度
    }
    if(sum_03 % 3 != 0) {    //先筛选判断,不能分成3等份直接 no  ,就不用dfs了
        printf("no\n");
    } else {
        dfs_03(0,0,0);
        if(f_03) { //可以拼成三条相同长度的木棍
            printf("yes\n");
        } else {
            printf("no\n");
        }
    }
    return;
}

 N皇后问题 (N个皇后不能处在同一行或同一列或同一斜线上)


主要问题 :斜线的标记如何判断  !
下标:
0 1 2 3 4 5 6 7
1
2
3
4
5
6
7

发现规律线主对角(y = -x) 行 - 列 == 定值1  , 副对角线 (y = x) 行 + 列 == 定值2      
且每条对角线的差值均不同 ,利用此点判断标记对角线是否有重复的皇后

//   8改成n,输入n就可以计算n皇后的解
int ans_04 = 0;
bool col_04[10],x1_04[20],x2_04[20];  //col_04为列  , x1_04为第一条对角线,x2_04为第二条对角
bool check_04(int r,int i) {  //行 ,每行所有位置
    return !col_04[i] && !x1_04[r + i] && !x2_04[r - i + 8];    //行或列上没有标记 即满足不重复,才可以放置皇后
}

void dfs_04(int r) {  //行循环标记
    if(r == 8) { //填完8行,为一种情况ans_04++
        ans_04++;
        return;
    }
    for(int i = 0; i < 8; i++) {
        if(check_04(r,i)) { //可以放 ,标记
            col_04[i] = x1_04[r + i] = x2_04[r - i + 8] = true;  //标记 第一条对角线x1_04 , 第二条对角线x2_04 ,r-i要 +8 是为了防止 r-i小于0
            dfs_04(r + 1); //递归寻找
            col_04[i] = x1_04[r + i] = x2_04[r - i + 8] = false;   //为了列出所有满足n皇后的情况,一定要取消标记,重新判断 ,回退
        }
    }
}


void test_04() {
    dfs_04(0);
    cout<< ans_04 << endl;
    return;
}

求一个n元高次方程的解


k1x1^p1 + k2x2^p2 + k3x3^p3 + k4x4^p4 +... + knxn^pn = 0;   x∈[1,M]  , i∈[1,n]  , p ∈[1,4] , k ∈[-20,20]均为整数

第一行输入一个整数n∈[1,4]  ,
第二行输入一个整数M∈[1,150]
接下来 3 到 n + 2行输入对应位置的系数和幂
计算方程解的个数


样例输入:
3
100
1 2
-1 2
1 2


输出:
104



#include<cstdio>
int k_05[5] , p_05[5];
int n_05 , M_05 , ans_05;
long long poww_05(int x,int y) {  //创次方函数 返回x^y ,注意数值大! long long   别用pow!!!
    long long ret = 1;
    for(int i = 0; i < y; i++) {
        ret *= x;
    }
    return ret;  //k_05[x]*返回的ret
}
void dfs_05(int x,long long s) {    //表示每项 , x表示未知数 , s表示累加到当前多项式的值
    if(x == n_05) { //遍历xn ,看是否满足方程
        if(s == 0) {
            ans_05++;
        }
        return;
    }
    for(int i = 1 ; i <= M_05; i++) { //未知数属于[1,M] ,遍历代入看是否满足方程
        dfs_05(x + 1,s + k_05[x] * poww_05(i,p_05[x]) );//递归 x1 -- xn    判断当x1~xn 选 0-M (M*M种)时 项式值的和 是否 == 0
    }                                        //poww(i,p[x]) == ret   return的返回值  ret
}
void test_05() {
    scanf("%d",&n_05); //x1 -- xn  n项式
    scanf("%d",&M_05); //x取值范围 遍历
    for(int i = 0; i < n_05; i++) {
        scanf("%d%d",&k_05[i],&p_05[i]);  //输入 系数 、幂      [写数组名仅代表第一个位置]
    }
    dfs_05(0,0); //x1--xn
    printf("%d\n",ans_05);
    /*测试
    for(int i = 0;i < n;i++){
    printf("%d %d\n",k_05[i],p[i]);
    }
    */
    return;
}

数独 (较难)


(每行或每列元素不重复 , 按九宫格分区,九宫格内为1-9不重复)

输入一张数独表
输出填完后的数独表

样例输入:

* 2 6 * * * * * *
* * * 5 * 2 * * 4
* * * 1 * * * * 7
* 3 * * 2 * 1 8 *
* * * 3 * 9 * * *
* 5 4 * 1 * * 7 *
5 * * * * 1 * * *
6 * * 9 * 7 * * *
* * * * * * * * *

输出:
1 2 6 7 3 4 5 9 8
3 7 8 5 9 2 6 1 4
4 9 5 1 6 8 2 3 7
7 3 9 4 2 5 1 8 6
8 6 1 3 7 9 4 2 5
2 5 4 8 1 6 3 7 9
5 4 7 2 8 1 9 6 3
6 1 3 9 4 7 8 5 2
9 8 2 6 5 3 7 4 1


char s_06[10][10];  //用ASCLL 减少空间复杂度
bool f_06;  //初始值 false 即 0
bool vx_06[10][10],vy_06[10][10],vv_06[10][10];   //vx_06为每行已经填的数字标记  ,vy_06为每列已经填的数字标记 , vv_06为九宫格3*3已经填的数字标记
void dfs_06(int x,int y) {    //下标
    //测试    printf("%d%d", x , y);

    if(f_06) { //已经找到   dfs是不断回溯  返回值 ,f开全局能保存是否找到  ,找到不断后退到最初的dfs(0,0),return退出
        return;
    }
    if(x == 9) { //到第九行  打印
        f_06 = true;
        for(int i = 0; i < 9; i++) {
            for(int j = 0; j < 9; j++) {
                if (j != 8) { //最后一个不留空格格式
                    printf("%c ",s_06[i][j]);
                } else {
                    printf("%c \n",s_06[i][j]);
                }
            }
        }
        return;
    }
    if(y == 9) {
        dfs_06(x + 1,0);        //遍历行 ,每行从下标0 元素开始  直到最后一行
        return;
    }
    if(s_06[x][y] != '*') {
        dfs_06(x,y + 1);
        return;
    }
    for(int i = 1; i <= 9; i++) { //遍历数字 1 - 9
        if(!vx_06[x][i] && !vy_06[y][i] && !vv_06[x / 3 * 3 + y / 3][i] ) {  //还未标记此数字 ,可以填入
            s_06[x][y] = '0' + i; //用char数组 存储  ASCLL码 映射填入 1 - 9
            vx_06[x][i] = true;
            vy_06[y][i] = true;
            vv_06[x / 3 * 3 + y / 3][i] = true;  //填每个小九宫格里的位置,完 再作为大格子里的位置
            dfs_06(x,y + 1);//遍历寻找合适值填入
            vx_06[x][i] = false;   //清除标记
            vy_06[y][i] = false;
            vv_06[x / 3 * 3 + y / 3][i] = false;
            s_06[x][y] = '*';
        }
    }
}
void test_06() {
    for(int i = 0; i < 9; i++) {
        for(int j = 0; j < 9; j++) {  //字符数组 单个读取!
            scanf(" %c",&s_06[i][j]); // 注意输入率格式有空格 :空%c  的输入格式才是对的   (第一个空不管,每行是%c结束的)
        }
    }
    for(int i = 0; i < 9; i++) {
        for(int j = 0; j < 9; j++) {
            //测试printf("%c ",s_06[i][j]);
            if(s_06[i][j] != '*') { //已经填好的
                vx_06[i][s_06[i][j] - '0'] = true;  //s_06[i][j] - '0' 为了 算出填好的元素 ,记录vx_06行中使用过的数字
                vy_06[j][s_06[i][j] - '0'] = true;    //记录vy_06列中使用过的数字
                vv_06[i / 3 * 3 + j / 3][s_06[i][j] - '0'] = true; //记录九宫格内使用过的数字
            }
        }//cout<<endl;
    }
    dfs_06(0,0); //填数独
    return;
}


双色皇后


输入棋盘大小n    (n*n)
接下来n行输入棋盘  0/1    (1表示可以放皇后 , 0则不行)  (小细节:遇到0就不能放状态标记用0表示不能再放 ,黑的用1)
有n个黑皇后和n个白皇后放入棋盘, 均满足  同一颜色皇后不能处在同一行或同一列或同一斜线上
输出一个整数,表示有多少种放法

样例输入:
4
1 1 1 1
1 1 1 1
1 1 1 1
1 1 1 1


输出:
2


int mp_07[10][10];
int vy_07[10],vd1_07[20],vd2_07[20]; // (行遍历不用vx) 需: 列 、 第一条对角线(行列和) 、第二条对角线(横列差+行数n 保证>0) 标记  !!!!!
int n_07,ans_07;
void dfs_07(int x,int p) {     //p判断先放白 后放黑    每轮中填入p在判断数组vy_07,vd1_07,vd2_07中 ,1 代表填入白  ,2代表填入黑
    if(x == n_07 && p == 2) { //两轮遍历完 x == n 表示有填完 有解
        ans_07++;
        return;
    }

    if(x == n_07) {  //0不能放了,找下一轮
        dfs_07(0,p + 1);
        return;
    }
    //遍历判断 寻找能放的位置 (此处以x行固定遍历每行所有元素,再遍历x行  ,当然也可以用y列 )
    for(int i = 0; i < n_07; i++) {
        //题目mp_07中为0不能放      3表示 寻找递归结束后p再 +1  ,p为3  说明白、黑已经各遍历过了,应该结束了
        //不能是p,p为1时放白,2时再放有标记为1的说明放白了,不能用 ==3,说明已经结束了不用再放了
        if( mp_07[x][i] && vy_07[i] != 3 && vy_07[i] != p && vd1_07[x + i] != 3 &&vd1_07[x + i] != p && vd2_07[x - i + n_07] != 3 &&vd2_07[x - i + n_07] != p ) {
            mp_07[x][i] = 0;//放入皇后标记状态(表示不能再放)
            vy_07[i] += p;
            vd1_07[x + i] += p;
            vd2_07[x - i + n_07] += p;
            dfs_07(x + 1, p);//递归
            vy_07[i] -= p;    //恢复状态(与状态改变前相等 ,之前 +p 现在就 -p),再次寻找ans_07
            vd1_07[x + i] -= p;
            vd2_07[x - i + n_07] -= p;
            mp_07[x][i] = 1;
        }
    }
}
void test_07() {
    scanf("%d",&n_07);
    for(int i = 0; i < n_07; i++) {
        for(int j = 0; j < n_07; j++) {
            scanf("%d",&mp_07[i][j]);   //输入格式问题!  空%d  或 %d  【%d空 ,表示空格结尾,导致要多输入一行】
        }
    }
    dfs_07(0,1);
    printf("%d\n",ans_07);
    return;
}

引爆炸弹  

n*m地图  放置这炸弹,手动引爆一个炸弹,会把炸弹所在的行和列的炸弹都引爆,连锁反应
求最少手动引爆多少个炸弹,可以把地图上的所有炸弹都引爆

输入地图大小n,m   ( 5为炸弹,0无炸弹)
输入地图


样例输入:

5 5
00010
00010
01001
10001
01000

输出:
2


int n_08,m_08,cnt_08; 
char s_08[1005][1005];  //字符串读效率高,减少时间复杂度
bool vx_08[1005],vy_08[1005];
void dfs_08(int x,int y) {
    s_08[x][y] = '0'; //找到炸弹,清为0 ,避免重复搜索
    if(!vx_08[x]) { //遍历行
        vx_08[x] = true;
        for(int i = 0; i < m_08; i++) {
            if(s_08[x][i] == '1') {
                dfs_08(x,i);
            }
        }
    }
    if(!vy_08[y]) { //遍历列
        vy_08[y] = true;
        for(int i = 0; i < n_08; i++) {
            if(s_08[i][y] == '1') {
                dfs_08(i,y);
            }
        }
    }
}


void test_08() {    //O(n*m)

    scanf("%d%d",&n_08,&m_08);
    for(int i = 0; i < n_08; i++) {
        scanf("%s",s_08[i]);  //格式为每行连续  可为字符串 减少读取时间 
    }
    for(int i = 0; i < n_08; i++) {
        for(int j = 0; j < m_08; j++) {
            if(s_08[i][j] == '1') {
                cnt_08++;
                dfs_08(i,j);
            }
        }
    }
    printf("%d\n",cnt_08);
    return;
}


int main() {
    test_08();
    return 0;
}


 

八皇后的解统计

 数独

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值