试题 历届试题 带分数 dfs+剪枝/全排列

问题描述

100 可以表示为带分数的形式:100 = 3 + 69258 / 714。

还可以表示为:100 = 82 + 3546 / 197。

注意特征:带分数中,数字1~9分别出现且只出现一次(不包含0)。

类似这样的带分数,100 有 11 种表示法。

输入格式

从标准输入读入一个正整数N (N<1000*1000)

输出格式

程序输出该数字用数码1~9不重复不遗漏地组成带分数表示的全部种数。

注意:不要求输出每个表示,只统计有多少表示法!

样例输入1
100
样例输出1
11
样例输入2
105
样例输出2
6
解题思路:
思路1:用dfs暴力搜索,加上适当条件剪枝以降低时间复杂度。可以看到带分数的形式:
n = a + b/c 
首先有 a<n;b/c应为正整数,所以b%c==0且b>c(b数字长度大于等于c)。
利用上述条件作为剪枝条件。
实现时需要先计算n的长度,以此确定a的最大长度(其实对于a用大于等于n长度的都可以,因为最后a>=n的会被剪枝)和b的最小长度(大于等于c的长度)
另外用count()函数计算num[]某区间按顺序组成的数字,注意区间是左闭右开。[ )
实现代码:
#include<cstdio>

const int Length = 9;//1-9

//输入
int n;

int num[Length];      //保存数字1-9
bool flag[Length+1]; //数字i是否被使用 
int res = 0;        //保存带分数表示法数目 
int length;           //输入n的位数(长度) 

//计算num[]从[start,end)的元素排列 
int count(int start,int end)
{
    int s = 0;
    for( int i=start; i<end; i++ )
    {
        s = s*10 + num[i];
    }
    return s;
}

//计算某一排列下带分数的个数 
void rec()
{
     
    for( int i=1; i<=length; i++ )
    {
        int a = count(0,i); //num[k](0<=k<i)的下标元素排列 
        if( a>=n )    return;//剪枝
        for( int j = i+(length-i)/2+1; j<Length; j++ )
        {    // j-i >= (Length-i)/2 因为b的长度>=c的长度  但因为count()是右开,所以再+1 
           // j<Length 最后给c至少留一位(要注意count()是右开 很容易出错) 
            int b = count(i,j); //num[k](i<=k<j)的下标元素排列 
            int c = count(j,Length); ////num[k](j<=k<Length)的下标元素排列 
if( b>c && b%c==0 && n==a+b/c ){ res++; } } } } //dfs找到所有排列 void dfs(int k) { //找到了某种全排列 if( k==Length ){ rec(); return; } //找全排列 for( int i=1; i<=Length; i++ ) { if( flag[i] ) continue; //数字i已经在num[]里了 跳过这次循环 flag[i] = true; num[k] = i; dfs( k+1 ); flag[i] = false; //回溯 num[]元素可以直接覆盖 所以不用回溯 } } void solve() { //计算n的位数 int n_ = n; while( n_ ) { length++; n_ /= 10; } dfs( 0 ); //num[]从下标0开始找全排列 printf("%d ",res); } int main() { scanf("%d",&n); solve(); return 0; }

思路2:与思路1大体相同,唯一不同的是其num[] 1-9的全排列是用C++全排列函数next_permutation(int *begin,int *end)  参考https://www.cnblogs.com/cstdio1/p/11311500.html

在<algorithm>下,返回bool类型值,当没有下一排列时返回false,参数为需要排列的地址(左闭右开),其next表示字典序从小到大(对应有prev_permutation())

注意:用next_permutation()求全排列时数组初始为升序排列。

#include<cstdio>
#include<algorithm>
using namespace std;

const int Length = 9;//一位数1-9

//输入
int n;

int length;//n位数 
int num[Length]; 
int ans; //计算符合条件的数目 

//计算num[]从[start,end)元素和 
int count(int start,int end)
{
    int s = 0;
    for( int i=start; i<end; i++ )
    {
        s = s*10 + num[i];
    }
    return s;
}

void solve()
{
    //初始化数字数组及输入n位数 
    for( int i=0; i<Length; i++){
        num[i] = i+1;
    } 
    
    int n_ = n;
    while( n_ )
    {
        length++;
        n_ /= 10;
    }
    
    do
    {
        for( int i=1; i<=length; i++ )// x最大为length位数 
        {
            int a = count(0,i);//遍历length位数的所有数字
            if( a<n )
            {//剪枝 
                for( int j=i+(length-i)/2+1; j<Length; j++ )// j-i>=(length-i)/2
                { 
                    int b = count(i,j), c = count(j,Length);
                    if( b>c && b%c==0 && n==a+b/c )
                    {
                        ans++;
                    }    
                }
            }    
        }    
    }while( next_permutation(num,num+Length));//num的全排列 
    
    printf("%d
",ans);
} 

int main()
{
    scanf("%d",&n);
    
    solve();
    
    return 0;
}
 
原文地址:https://www.cnblogs.com/w-like-code/p/13333896.html