第一周算法训练(dfs剩余题目)

本文介绍了在算法竞赛中如何运用DFS解决自然数拆分、N皇后问题和PERKET问题,以及单词接龙问题的技巧,展示了递归和深度优先搜索在这些问题中的应用。
摘要由CSDN通过智能技术生成

目录

第三题:P2404 自然数的拆分问题

第四题:N皇后问题

第五题:P2036 [COCI 2008/2009 #2] PERKET

第六题:单词接龙


第三题:P2404 自然数的拆分问题

https://www.luogu.com.cn/problem/P2404,这个是题目的链接

题解:首先分析题目,让我们求自然数的拆分,首先我们想到的会是循环,但是拿循环做的话我们要写很多行,而且由于数据不是很大,因此我们可以考虑用dfs,这样可以求出每一种情况,那么好,这时我们需要判断我们的代码怎么写了,首先我们看到数据2<=n<=8,因此我们可以通过数组来存储每一个子元素,而最大是8,因此这个数组的大小只要在8以上就可以了,那么我们现在来看AC代码

#include<stdio.h>
int n,arr[10];//定义一个存储每一位的数组arr
void print(int z)//打印函数,传递的参数z为元素个数
{
    for(int i=0;i<z;i++)
    {
        if(i==0)
        {
            printf("%d",arr[i]);//第一个元素不用打印前面的+号
        }
        else
        {
            printf("+%d",arr[i]);//后面的元素需要打印前面的加号
        }
    }
    printf("\n");//输出换行符
}
void dfs(int x,int y,int z)//每一层递归都是一层单层循环操作
//x表示当前层数所用数字的大小,y表示所用所有数字之和,z表示已经用过的元素个数
{
    if(x==n)//防止出现单个数就等于n的情况
    {
        return ;
    }
    if(y==n)//结束条件,当所有数字总和为n的结束递归函数
    {
        print(z);
        return ;
    }
    for(int i=x;i<=n-y;i++)
//循环从x开始,是因为要从当前层所用数字大小开始循环,这样才不会出现大数在小数前面的情况
//同时嘞要防止总和超过n,所以i<=n-y;
    {
        arr[z]=i;//将当前层数的值存储到数组当中
        dfs(i,y+i,z+1);//调用递归函数,这里无需回溯,因为是直接赋值的
    }
}
int main()
{
    scanf("%d",&n);//输入自然数
    dfs(1,0,0);//调用递归函数
    return 0;
}

第四题:N皇后问题

https://www.luogu.com.cn/problem/P1219

N皇后也可以用多层嵌套循环来完成,但是不如用dfs更加简洁,并且他们的时间复杂度是一样的

(每一层递归调用都是一层for循环)

题解:首先呢,我们可以将N皇后问题抽象成一种树形结构,第n层递归就是要将第n个皇后放在第n行,我们在放置的过程中只需要思考当前行,列,左下到右上的对角线,以及左上到右下的对角线上是否存在别的皇后与当前皇后相冲。话不多说直接看AC代码

#include <stdio.h>
#include <stdlib.h>

int a[15],b[15],c[40],d[40];
//a数组代表行,b数组代表列,c数组代表从左下到右上的对角线,d数组代表从左上到右下的对角线
int n,count=0;//n是棋盘大小,count是解的总数
void print()//输出函数,用于输出前三个解
{
    count++;//每进入一次这个输入函数,解的个数加一
    if(count<=3)//通过判断条件,只输出前三个解
    {
        for(int i=1;i<=n;i++)//输出每一行中皇后所在的位置
        {
            printf("%d ",a[i]);
        }
        printf("\n");
    }
}
void dfs(int i)//递归函数,里面的参数代表第i行的皇后该进行怎样的操作
{
    if(i>n)//当i超过了n,就说明已经将棋盘摆满皇后了,可以输出皇后位置了
    {
        print();//调用print函数
        return ;
    }
    for(int j=1;j<=n;j++)//j代表列
    {
        if(b[j]==0&&c[i+j]==0&&d[i-j+n]==0)//判断当前列以及两条对角线是否有皇后相冲
        {
            a[i]=j;//第i行的皇后在第j列
            b[j]=1;//进行标记
            c[i+j]=1;
            d[i-j+n]=1;
            dfs(i+1);//递归函数
            b[j]=0;//进行回溯
            c[i+j]=0;
            d[i-j+n]=0;
        }
    }
    return ;
}
int main()
{
    scanf("%d",&n);//输入棋盘大小
    dfs(1);//调用递归函数,
    printf("%d\n",count);//输出解的个数
    return 0;
}

第五题:P2036 [COCI 2008/2009 #2] PERKET

https://www.luogu.com.cn/problem/P2036

题解:其实这题有点01背包的感觉但主要思路还是用到了dfs,我们对每一种食物都只有两种情况,取这种食物,或者不取这个食物,然后就是二叉树进行搜索就可以了

直接来看AC代码

#include <stdio.h>
#include <math.h>

int s[15],b[15];
int n,sum=99999999;
int min(int x,int y)//判断最小值的函数
{
    return x<y?x:y;
}
void dfs(int i,int p,int q)//递归函数
//i代表取到哪一个食物了,p代表酸度值,q代表苦度值
{
    if(i>n)//当i大于n时代表所有的食物都已经轮到过一遍了,可以收集结果了
    {
        if (p==1&&q==0)
//起到一个特殊判断的情况,不能取到赋值的酸度和苦度,要不然最小值直接为1,没有比较的意义了
        {
            return;
        }
        sum=min(abs(p-q),sum);//收集最小的结果
        return ;
    }
//两种情况,取还是不取
    dfs(i+1,p,q);
    dfs(i+1,s[i]*p,q+b[i]);

}
int main()
{
    scanf("%d",&n);//输入有多少种食物
    for(int i=1; i<=n; i++)
    {
        scanf("%d%d",&s[i],&b[i]);//输入每种食物的酸度和苦度
    }
    dfs(1,1,0);//进行递归的调用
    printf("%d",sum);输出最小的结果
    return 0;
}
第六题:单词接龙

https://www.luogu.com.cn/problem/P1019

这题其实还是比较难的,这道题还有一些小细节也会卡住,那先来说这个小细节

细节:

1.接龙的时候,两个单词取的是最小重叠部分

2.每个单词至多用两次

3.不能存在包含关系,若一个单词,包含另一个单词,则不能进行接龙

题解:首先我们需要先判断是否能够接龙,我们这题只需要计算长度,而并没有问你接龙最长的单词是什么,所以在判断函数里面,我们只需要返回能够增加的长度就好。反复通过深搜就可以得到最后的最长长度

#include <stdio.h>
#include <string.h>

char a[25][100];
int b[25]; // 用于统计次数
int c[25]; // 用于统计每个字符长度
int n, sum, max, max1;
//n是单词数,sum是长度,max是递归中的最大值,max1是要输出的最大值
char ch;//开头的字母

int find(int x, int y)//用于判断是否能够进行接龙的函数
{
    int len1 = c[x];//记录第x个单词的长度
    int len2 = c[y];//记录第y个单词的长度
    for(int i = len1 - 1; i >= 0; i--)//从第x单词的结尾开始遍历,判断是否等于y单词的开头
    {
        if(a[x][i] == a[y][0])
        {
            int k = 0;
            for(int j = i + 1; j < len1; j++)//判断重合的第一个字母的后面是否重合
            {
                k++;
                if(a[x][j] != a[y][k])//如果后面出现了不能重合的部分,那么这两个单词不能接龙
                {
                    return 0;
                }
            }
            return len2 - (k + 1);//返回能够增加的长度
        }
    }
    return 0;
}

void dfs(int x)//深度搜索每一个单词,x代表搜索第x个单词
{
    int temp = sum;//记录当前长度
    for(int i = 0; i < n; i++)//遍历集合中的所有单词
    {
        if(b[i] < 2 && find(x, i))//当这个单词没用过两次,并且能够接龙
        {
            sum += find(x, i);//sum加上接龙的长度
            b[i]++;//增加单词使用次数
            dfs(i);//进行递归
            sum -= find(x, i);//回溯
            b[i]--;
        }
    }
    if(sum > max)//如果说长度大于递归记录的最大值
    {
        max = sum;//更新递归中的最大值
    }
    sum = temp;//回溯到之前的长度
}

int main()
{
    scanf("%d", &n);//输入单词数
    for(int i = 0; i < n; i++)//输入每一个单词
    {
        scanf("%s", a[i]);
        getchar();
        c[i] = strlen(a[i]);//记录每一个单词的长度
    }
    scanf(" %c", &ch);//注意!!!这个地方前面要有一个空格,去吸收缓冲区的字符
    for(int i = 0; i < n; i++)
    {
        if(a[i][0] == ch)//首先单词的首字母要能等于开头“龙”
        {
            sum = c[i];//当前单词的长度
            b[i]++;//当前单词已经用过一次了
            dfs(i);//进行递归,开始进行接龙
            b[i]--;//回溯,取消掉这一次的使用
            if(max > max1)//当递归得到的结果,比初识的最大值大的时候,要将最大值更新
            {
                max1 = max;
            }
        }
    }
    printf("%d\n", max1);//输出最小值
    return 0;
}
最后小结:

如果你真的很努力很努力很努力了
但结果依旧平凡
那就接受它吧
再渴望的学校 总有人不费吹灰之力就读了
再拉胯的学校 也有人复读了一整年才考上
这个世界上有很多事情
努力了也不一定会有结果
很多事情拼尽全力
只是为了不留遗憾

优不优秀都是相对的
渴望了 但怯懦了
遗憾就是绝对的了

致每一个在默默努力的人

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值