P2404 自然数的拆分问题

在这里插入图片描述
说明/提示
用回溯做。。。。

N<=8

题目本身倒不难,但是这道题真的把我坑死了…, 可能是还掌握的不太好,看到这种题目很快联想到之前做过的一题,给定 n个数,从中选出任意个数可以组合成 m 这n个数字中的那些数字可以重复选择,我灵机一动,这不就是和那题是一样的吗,给定n-1个数,分别为1----n-1,从中选出任意个数字使其和为n,就拿n=4来举例子8~

在这里插入图片描述

不太会画画,凑合看8~

从图中我们可以看到,这其中会有重复的,比如以1开头的有 1 1 3 ,然后以3的时候又有了3 1 1,这是不符合题意的,所以每次有一个结果以后,我们要将其保存起来,下次次再遇到符合条件的,我们首先比较一下之前是否有这样的方案,如何解决呢?我们可以先排序,然后再依次比较每一个数字,如果每一个数字都相同,俺么我们就可以断定此方案在之前已经出现过了,就不必再输出。此方案有些麻烦哈,不要急,最好先理解一下这个方案,后面还会详细讲解优化的方法。AC代码~

#include<iostream>
#include <vector>
#include <algorithm>
using namespace std;
int sum(vector<int> f);
vector<int> t;
vector<vector<int > > res;
bool check(vector<int> t); //去重
void dfs(int n);

    int main()
    {
        int n;
        cin>>n;
        dfs(n);
        return 0;
    }


    void dfs(int n)
    {
        if(sum(t)>=n) //如果所选数字已经超过 n了
        {
            if(sum(t)==n) //如果等于n
            {
                if(check(t)) {  //检查一下之前是否已经输出过,如果没有的话,那就输出

                    for (vector<int>::iterator it = t.begin(); it != t.end(); it++) {
                        if (it != t.end() - 1) {
                            cout << *it << "+";
                        } else {
                            cout << *it;
                        }
                    }
                    res.push_back(t);
                    cout<<endl;
                }


            }
            return ;
        }

        for(int i=1;i<=n-1;i++) //1--n-1个数字中选数字
        {
            t.push_back(i);
            dfs(n);
            t.pop_back();
        }
    }

    int sum(vector<int> f) //求f中的和
    {
        int s=0;
        for(vector<int >::iterator it=f.begin();it!=f.end();it++)
        {
            s+=*it;
        }
        return s;
    }


    bool check(vector<int> t) //去重,看看有没有重复的
    {
        for(vector<vector<int> >::iterator it=res.begin();it!=res.end();it++)
        {
            bool flag=true;
            if(it->size()==t.size())
            {
                sort(it->begin(),it->end());
                sort(t.begin(),t.end());
                for(int i=0;i<=t.size()-1;i++)
                {
                    if((*it)[i]!=t[i])
                    {
                        flag=false;
                        break;
                    }

                }
            }
            else
            {
                flag=false;
            }
            if(flag)
            {
                return false;
            }
        }
        return true;
    }

通过观察,我们可以发现,上面每一个数字,前面一个数都小于等于后面一个数字,所以,我们可以这样,只有当前面一个数小于等于后面一个数的时候,我们才其push进容器里面,否则我们就不加入,当然如果此时的容器为空的话,那可以直接加入到里面来,这样就避免了重复,可以不用专门写一个判重的函数,下面是AC代码~

#include<iostream>
#include <vector>
#include <algorithm>
using namespace std;
int sum(vector<int> f);
vector<int> t;
vector<vector<int > > res;
bool check(vector<int> t); //去重
void dfs(int n);

    int main()
    {
        int n;
        cin>>n;
        dfs(n);
        return 0;
    }


    void dfs(int n)
    {
        if(sum(t)>=n) //如果所选数字已经超过 n了
        {
            if(sum(t)==n) //如果等于n
            {
                    for (vector<int>::iterator it = t.begin(); it != t.end(); it++) {
                        if (it != t.end() - 1) {
                            cout << *it << "+";
                        } else {
                            cout << *it;
                        }
                    }
                    res.push_back(t);
                    cout<<endl;
            }
            return ;
        }

        for(int i=1;i<=n-1;i++) //1--n-1个数字中选数字
        {
            if(t.empty()||(!t.empty()&&i>=t[t.size()-1])) //如果此时的t为空 或者 t为空并且此时加入的数字要比t里面的最后一个数字大
            {
                t.push_back(i);
                dfs(n);
                t.pop_back();
            }
        }
    }

    int sum(vector<int> f) //求f中的和
    {
        int s=0;
        for(vector<int >::iterator it=f.begin();it!=f.end();it++)
        {
            s+=*it;
        }
        return s;
    }

网上的代码似乎都不怎么好理解,其实也很容易,和我的代码差不多,只不过他有些代码是拐了弯的,关键位置没有进行解释说明,所以才会感觉有点晦涩难懂,下面我解释一下某一位大佬的AC代码

#include<iostream>
#include<cstdio>
using namespace std;
int n, p[11]={1}, cnt=1, m;
void print(int aa){//输出方案
    for(int i=1; i<aa; i++)
        cout<<p[i]<<"+";
    cout<<p[aa]<<endl;
}
void dfs(int a){//通过DFS得到排列, a计数
    for(int i=p[a-1]; i<=m; i++){//回溯后跳出分支
        if(i==n) continue;//防止最后一行输出n
        p[a]=i;
        m-=i;
        if(m==0) print(a);//m减完时,该方案已排列完毕,进行输出
        else dfs(a+1);//否则继续搜索
        m+=i;//回溯
    }
}
int main(){
    cin>>n;
    m=n;
    dfs(1);
    return 0;
}

说实话,刚开始我看到这段代码直接蒙圈了,根本看不懂写的啥,还好机智如,画了画图就很清晰了~~我们先来看看这段代码~

void dfs(int a){//通过DFS得到排列, a计数
    for(int i=p[a-1]; i<=m; i++){//回溯后跳出分支
        if(i==n) continue;//防止最后一行输出n
        p[a]=i;
        m-=i;
        if(m==0) print(a);//m减完时,该方案已排列完毕,进行输出
        else dfs(a+1);//否则继续搜索
        m+=i;//回溯
    }

其实上面的代码可以改写为:

void dfs(int a){//通过DFS得到排列, a计数
    for(int i=1; i<=m; i++){//回溯后跳出分支
        if(i==n||i>p[a-1]) continue;//为了防止输出n ,现在加入的数字必须要小于等于数组的最后一个数
        p[a]=i;
        m-=i; //因为加入了一个i,所以m要减少i
        if(m==0) print(a);//m减完时,该方案已排列完毕,进行输出
        else dfs(a+1);//否则继续搜索
        m+=i;//回溯
    }

上面的代码中a表示层数,也就是dfs到了第几层,m代表选完以后剩下的值,有人问既然你在if里面判断i==n,那还不如直接在for循环里面把i<=m改写成i<m,。。。。。想法虽好,但是这种改写是错误的,就比如当n=4的时候,i=1;i<m,p[1]=1,此时m=3,再次进入dfs,i=1;i<3,此时i再也无法再选取3了,那就是说当第一次选了1以后,后面最大值不能超过2,显然结果是错误的~,上面是我改写的代码,网上的那份代码里面的写法是int i=p[a-1],因为p里面存放的是所选的序列,所以~~~这里其实是在保证所要放进去的i一定是大于等于前面的那一个数字。

好了,至此为止,该介绍的都介绍完了,下次做dfs记得抽象成一棵树来做.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值