LDU 软件工程 算法分析与设计(三)- 递归与分治

问题 A: 集合划分问题

题目描述

包含n个元素的集合,可以划分为若干个非空子集。例如,当n=3时,集合{1,2,3} 可以构造如下五个划分: 

{1,2,3}                 {1},{2},{3} 

{1,2},{3}                    {1,3},{2}                    {1},{2,3} 

请设计程序,计算包含n个元素的集合可以构造多少个不同的划分。 

输入

一个整数n,(1<=n<=25)

输出

一个整数

样例输入

1

样例输出

1

思想:①贝尔数,集合划分是贝尔数的一个问题。②DP,dp[i][j]代表是将i个数分到j个集合内

dp[i][j]=dp[i-1][j]*j+dp[i-1][j-1]  意思是当前有i-1个数和j个集合,你将第i个数放到j个集合中的一个有个j中选择,或者是当前这个数单独放一个集合。

long long B[30];
long long T[30];
const int N = 26;
void Bell()  
{  
    B[0] = 1;  
    B[1] = 1;  
    T[0] = 1;  
    for(int i=2;i<N;i++)  
    {  
        T[i-1] = B[i-1];  
        for(int j=i-2;j>=0;j--)  
            T[j] = (T[j]+T[j+1]);  
        B[i] = T[0];  
    }  
}   
int main()
{
    Bell();
    int temp;
    scanf("%d",&temp); 
    printf("%lld\n",B[temp]);
    return 0;
} 

问题 B: 楼梯台阶问题

题目描述

有一楼梯共M级,刚开始时你在第一级,若每次只能跨上一级或二级,要走上第M级,共有多少种走法?

输入

第一行一个整数T,表示测试组数。 
接下来T行每行一个整数M(1<=M<=40) 

输出

输出T行 
每行一个整数 

样例输入

1
4

样例输出

3

思想:第 i 阶台阶由i-1跨一步或者i-2跨2步过来,所以dp[i]=dp[i-1]+dp[i-2].预处理下dp[1] dp[2] dp[3]即可

对于第一阶台阶是0还是1,假如考虑在1不动算一种情况就是1 否则就就是0

问题 C: 小蜜蜂

题目描述

有一只经过训练的蜜蜂只能爬向右侧相邻的蜂房,不能反向爬行。请编程计算蜜蜂从蜂房a爬到蜂房b的可能路线数。其中,蜂房的结构如下所示。

 

输入

一个整数T表示测试组数。 
接下来T行,每行两个整数a和b(0<a<b<50) 

输出

T行 
每行一个整数 

样例输入

2
1 2
3 6

样例输出

1
3

思想:考虑下对于任何点来说它只能通过相邻的2个过来,所以就可以考虑枚举下每个点的贡献,每个点只能对于+1 +2有贡献.

ll dp[60];
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        memset(dp,0,sizeof(dp));
        int a,b;
        scanf("%d%d",&a,&b);
        dp[a+1]=1;
        dp[a+2]=2;
        for(int i=a+3;i<=b;i++)
        {
            dp[i]=dp[i-1]+dp[i-2];
        }
        printf("%d\n",dp[b]);
    }
    return 0;
}

问题 D: ​切面条问题

题目描述

一根高筋拉面,中间切一刀,可以得到2根面条。如果先对折1次,中间切一刀,可以得到3根面条。如果连续对折2次,中间切一刀,可以得到5根面条。那么,连续对折n次,中间切一刀,会得到多少面条呢?

输入

一个整数n(1<=n<=1000000)

输出

一个整数,注意,由于答案非常大,要求你输出答案模除1000000007之后的结果

样例输入

3

样例输出

9

思想:单独考虑的每一个话,稳的T,可以将每个点可以推出来一个很好推的式子(毛线) 对于每个n来说 他就等于2^n+1.

无论是线性跑n还是快速幂都OK

问题 E: 整数划分问题

题目描述

计算将一个给定的正整数划分为一系列正整数的和的方案数,称为整数的划分问题,例如,当给定正整数为6时,可以有如下划分: 

6=6; 

6=5+1; 

6=4+2=4+1+1; 

6=3+3=3+2+1=3+1+1+1; 

6=2+2+2=2+2+1+1=2+1+1+1+1; 

6=1+1+1+1+1+1+1。 

如果用f(n)代表正整数n的划分数,则f(6)=11 

现在,给你数字n,要求你计算f(n) 

输入

一个整数n

输出

输出f(n)。注意,由于答案非常大,你的答案需要输出模除1000000007之后的结果

样例输入

6

样例输出

11

思想:母函数算法的模板题,如果看到此博客可以学一下母函数,会在其中注释代码,仍有不懂可以提问。

const long long mod = 1e9+7;
const int maxn= 1e5+5;
long long c1[maxn];
long long c2[maxn];
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=0;i<=n;i++)//c1是储存每个的结果 c2是每次计算的结果
    {
        c1[i]=1;   //初始化对于每一项的系数都是1 
        c2[i]=0;
    }
    for(int i=2;i<=n;i++)//枚举项数 总共有n个式子
    {
        for(int j=0;j<=n;j++)//枚举前边的到的所有项数
        {
            for(int k=0;k+j<=n;k+=i)//对于第i个式子的每个式子的系数 所以是每次加i
                c2[k+j]=(c1[j] + c2[k+j])%mod;  //k+j是乘完之后的次方
        }
        for(int k=0;k<=n;k++)//进行替换
        {
            c1[k]=c2[k];
            c2[k]=0;
        }
    }
    printf("%lld\n",c1[n]%mod);
    return 0;
} 

问题 F: 找单词(母函数)

题目描述

假设有x1个字母A, x2个字母B,..... x26个字母Z,同时假设字母A的价值为1,字母B的价值为2,..... 字母Z的价值为26。那么,对于给定的字母,可以找到多少价值<=50的单词呢?单词的价值就是组成一个单词的所有字母的价值之和,比如,单词ACM的价值是1+3+14=18,单词HDU的价值是8+4+21=33。(组成的单词与排列顺序无关,比如ACM与CMA认为是同一个单词)。

输入

输入首先是一个整数N,代表测试实例的个数。
然后包括N行数据,每行包括26个<=20的整数x1,x2,.....x26.

输出

对于每个测试实例,请输出能找到的总价值<=50的单词数,每个实例的输出占一行。

样例输入

2
1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
9 2 6 2 10 2 2 5 6 1 0 2 7 0 2 2 7 5 10 6 10 2 10 6 1 9

样例输出

7
379297

思想:Hdu上的母函数的入门题。跟上边的那个差不多只是一些东西稍微改了下,思想还是一样的,套上板子改改就好了。

对于母函数不懂的可以看上题题解,看懂在看此题更好。

const int maxn= 60;
long long c1[maxn];
long long c2[maxn];
int num[maxn];
int main()
{
    int t; 
    scanf("%d",&t);
    while(t--)
    {
        for(int i=1;i<=26;i++)//输入每种的数量
            scanf("%d",&num[i]);
        memset(c1,0,sizeof(c1));
        memset(c2,0,sizeof(c2)); 
        for(int i=0;i<=num[0];i++)//将第一个的为1 和 当初的那个n一样
            c1[i]=1;    
        for(int i=1;i<=26;i++)//枚举项 就是那个26项
        {
            for(int j=0;j<=50;j++)//因为要求不超过50 所以最大的权值应该是50
                for(int k=0;k<=num[i]*i &&  k+j<=50;k+=i)//k的值应该小于等于最大的那个 而且 不会超过50
                    c2[k+j]=(c1[j] + c2[k+j]);  
            for(int k=0;k<=50;k++)
            {
                c1[k]=c2[k];
                c2[k]=0;
            }
        }
        long long ans=0;
        for(int i=1;i<=50;i++)//所有的求一个和
            ans+=c1[i];
        printf("%lld\n",ans);
    }
    return 0;
} 

 

专题十:算法分析设计 1.常用的算法设计方法:   1.1 迭代法   1.2 穷举搜索法   1.3 递推法   1.4 递归法   1.5 贪婪法   1.6 分治法   1.7 动态规划法   1.8 回溯法 算法基础部分: 算法是对特定问题求解步骤的一种描述,算法是指令的有限序列,其中每一条指令表示一个或多个操作。 算法具有以下5个属性:   有穷性:一个算法必须总是在执行有穷步之后结束,且每一步都在有穷间内完成。   确定性:算法中每一条指令必须有确切的含义。不存在二义性。只有一个入口和一个出口   可行性:一个算法是可行的就是算法描述的操作是可以通过已经实现的基本运算执行有限次来实现的。   输入:一个算法有零个或多个输入,这些输入取自于某个特定对象的集合。   输出:一个算法有一个或多个输出,这些输出同输入有着某些特定关系的量。 所以对应的算法设计的要求:   正确性:算法应满足具体问题的需求;   可读性:算法应该好读,以有利于读者对程序的理解;   健壮性:算法应具有容错处理,当输入为非法数据,算法应对其作出反应,而不是产生莫名其妙的输出结果。   效率存储量需求:效率指的是算法执行的间;存储量需求指算法执行过程中所需要的最大存储空间。一般这两者问题的规模有关。 1.1 迭代法: 迭代法是用于求方程或方程组近似根的一种常用的算法设计方法。设方程为f(x)=0,用某种数学方法导出等价的形式x=g(x),然后按以下步骤执行: (1)选一个方程的近似根,赋给变量x0; (2)将x0的值保存于变量x1,然后计算g(x1),并将结果存于变量x0; (3)当x0x1的差的绝对值还小于指定的精度要求,重复步骤(2)的计算。 若方程有根,并且用上述方法计算出来的近似根序列收敛,则按上述方法求得的x0就认为是方程的根。上述算法用C程序的形式表示为: 【算法】迭代法求方程的根 { x0=初始近似根; do { x1=x0; x0=g(x1); /*按特定的方程计算新的近似根*/ } while ( fabs(x0-x1)>Epsilon); printf(“方程的近似根是%f\n”,x0); } 迭代算法也常用于求方程组的根,令 X=(x0,x1,…,xn-1) 设方程组为: xi=gi(X) (I=0,1,…,n-1) 则求方程组根的迭代算法可描述如下: 【算法】迭代法求方程组的根 { for (i=0;i<n;i++) x[i]=初始近似根; do { for (i=0;i<n;i++) y[i]=x[i]; for (i=0;i<n;i++) x[i]=gi(X); for (delta=0.0,i=0;i<n;i++) if (fabs(y[i]-x[i])>delta) delta=fabs(y[i]-x[i]); } while (delta>Epsilon); for (i=0;i<n;i++) printf(“变量x[%d]的近似根是 %f”,I,x[i]); printf(“\n”); } 具体使用迭代法求根应注意以下两种可能发生的情况: (1)如果方程无解,算法求出的近似根序列就不会收敛,迭代过程会变成死循环,因此在使用迭代算法前应先考察方程是否有解,并在程序中对迭代的次数给予限制; (2)方程虽然有解,但迭代公式选择不当,或迭代的初始近似根选择不合理,也会导致迭代失败。 1.2 穷举搜索法: 穷举搜索法是对可能是解的众多候选解按某种顺序进行逐一枚举和检验,并从中找出那些符合要求的候选解作为问题的解。 要解决的问题只有有限种可能,在没有更好算法总可以用穷举搜索的办法解决,即逐个的检查所有可能的情况。可以想象,情况较多这种方法极为费。实际上并不需要机械的检查每一种情况,常常是可以提前判断出某些情况不可能取到最优解,从而可以提前舍弃这些情况。这样也是隐含的检查了所有可能的情况,既减少了搜索量,又保证了不漏掉最优解。 【问题】 将A、B、C、D、E、F这六个变量排成如图所示的角形,这六个变量分别取[1,6]上的整数,且均不相同。求使角形条边上的变量之和相等的全部解。如图就是一个解。 程序引入变量a、b、c、d、e、f,并让它们分别顺序取1至6的整数,在它们互不相同的条件下,测试由它们排成的如图所示的角形条边上的变量之和是否相等,如相等即为一种满足要求的排列,把它们输出。当这些变量取尽所有的组合后,程序就可得到全部可能的解。细节见下面的程序。 # include <stdio.h> void main() { int a,b,c,d,e,f; for (a=1;a<=6;a++) //a,b,c,d,e依次取不同的值 for (b=1;b<=6;b++) { if (b==a) continue; for (c=1;c<=6;c++) { if (c==a)||(c==b) continue; for (d=1;d<=6;d++) { if (d==a)||(d==b)||(d==c) continue; for (e=1;e<=6;e++) { if (e==a)||(e==b)||(e==c)||(e==d) continue; f=21-(a+b+c+d+e);//最后一个用减法算 if ((a+b+c==c+d+e))&&(a+b+c==e+f+a)) { printf(“%6d,a); printf(“%4d%4d”,b,f); printf(“%2d%4d%4d”,c,d,e); scanf(“%c”); } } } } } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值