《算法竞赛入门经典》Chap2

思考题

题目1

假设需要输出2,4,6,8,…,2n,每个一行,能不能通过对程序2-1进行小小的改动来实现呢?为了方便,现把程序复制如下:

#include <cstdio>
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        printf("%d\n",i);
    return 0;
}

任务1

修改第7行,不修改第6行。

#include <cstdio>
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        printf("%d\n",2*i);
    return 0;
}

任务2

修改第6行,不修改第7行。

#include <cstdio>
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=2;i<=2*n;i+=2)
        printf("%d\n",i);
    return 0;
}

题目2

下面的程序运行结果是什么? “!=” 运算符表示“不相等”。
提示:请上机实验,不要凭主观感觉回答。

#include <cstdio>
int main()
{
    double i;
    for(i=0;i!=10;i+=0.1)
        printf("%.1f\n",i);
    return 0;
}

该题乍一看可能摸不着头脑,输出在本应结束循环的地方没有停止,我们将输出格式改为下式后原因便显而易见了。

printf("%.16f\n",i);

输出结果:

0.0000000000000000
0.1000000000000000
0.2000000000000000
0.3000000000000000
0.4000000000000000
0.5000000000000000
0.6000000000000000
0.7000000000000000
0.7999999999999999
0.8999999999999999
0.9999999999999999
1.0999999999999999
1.2000000000000000
1.3000000000000000
1.4000000000000001
1.5000000000000002
1.6000000000000003
1.7000000000000004
1.8000000000000005
1.9000000000000006
2.0000000000000004
2.1000000000000005
2.2000000000000006
2.3000000000000007
2.4000000000000008
2.5000000000000009
2.6000000000000010
2.7000000000000011
2.8000000000000012
2.9000000000000012
3.0000000000000013
3.1000000000000014
3.2000000000000015
3.3000000000000016
3.4000000000000017
3.5000000000000018
3.6000000000000019
3.7000000000000020
3.8000000000000020
3.9000000000000021
4.0000000000000018
4.1000000000000014
4.2000000000000011
4.3000000000000007
4.4000000000000004
4.5000000000000000
4.5999999999999996
4.6999999999999993
4.7999999999999989
4.8999999999999986
4.9999999999999982
5.0999999999999979
5.1999999999999975
5.2999999999999972
5.3999999999999968
5.4999999999999964
5.5999999999999961
5.6999999999999957
5.7999999999999954
5.8999999999999950
5.9999999999999947
6.0999999999999943
6.1999999999999940
6.2999999999999936
6.3999999999999932
6.4999999999999929
6.5999999999999925
6.6999999999999922
6.7999999999999918
6.8999999999999915
6.9999999999999911
7.0999999999999908
7.1999999999999904
7.2999999999999901
7.3999999999999897
7.4999999999999893
7.5999999999999890
7.6999999999999886
7.7999999999999883
7.8999999999999879
7.9999999999999876
8.0999999999999872
8.1999999999999869
8.2999999999999865
8.3999999999999861
8.4999999999999858
8.5999999999999854
8.6999999999999851
8.7999999999999847
8.8999999999999844
8.9999999999999840
9.0999999999999837
9.1999999999999833
9.2999999999999829
9.3999999999999826
9.4999999999999822
9.5999999999999819
9.6999999999999815
9.7999999999999812
9.8999999999999808
9.9999999999999805
10.0999999999999801
10.1999999999999797

不难看出,是精度出现了问题,导致 i!=10 没能正常结束循环,这个问题是由二进制浮点数算术标准(IEEE 754)导致的,在算法竞赛中我们可以将误差范围控制在10-6内以达到题目要求。
正确程序如下:

#include <cstdio>
int main()
{
    double i;
    for(i=0;i-10<1e-6;i+=0.1)
        printf("%.1f\n",i);
    return 0;
}

习题

习题2-1 水仙花数 (daffodil)

输出100~999中的所有水仙花数。若3位数ABC满足ABC=A3+B3+C3,则称其为水仙花数。例如153=13+53+33,所以153是水仙花数。
题解一:根据等式从左向右验证

#include <cstdio>
#include <cmath>
int main()
{
    for(int i=100;i<1000;i++)
    {
        if(i==pow(i/100,3)+pow(i/10%10,3)+pow(i%10,3))
        {
            printf("%d\n",i);
        }
    }
    return 0;
}

题解二:根据等式从右向左验证

#include <cstdio>
#include <cmath>
int main()
{
    for(int i=0;i<10;i++)
    {
        for(int j=0;j<10;j++)
        {
            for(int k=0;k<10;k++)
            {
                int m=pow(i,3)+pow(j,3)+pow(k,3);
                int n=i*100+j*10+k;
                if(100<=m&&m<=999&&m==n)
                {
                    printf("%d\n",n);
                }
            }
        }        
    }
    return 0;
}

习题2-2 韩信点兵 (hanxin)

相传韩信才智过人,从不直接清点自己军队的人数,只要让士兵先后以三人一排、五人一排、七人一排地变换队形,而他每次只掠一眼队伍的排尾就知道总人数了。输入包含多组数据,每组数据包含3个非负整数a,b,c,表示每种队形排尾的人数(a<3,b<5,c< 7),输出总人数的最小值(或报告无解)。已知总人数不小于10,不超过100。输入到文件结束为止。
样例输入:

2 1 6
2 1 3

样例输出:

Case 1: 41
Case 2: No answer
#include <cstdio>
int main()
{
    int t,f,s;
    int count=0;;
    while(scanf("%d%d%d",&t,&f,&s)==3)
    {
        count++;
        bool flag=false;
        for(int k=0;;k++)
        {
            int total3=7*k+s;
            if(total3<10) continue;
            if(total3>=100) break;
            for(int j=0;;j++)
            {
                int total2=5*j+f;
                if(total2<10) continue;
                if(total2>=100) break;
                for(int i=0;;i++)
                {
                    int total1=3*i+t;
                    if(total1<10) continue;
                    if(total1>=100)  break;
                    if(total1==total2&&total2==total3)
                    {
                        flag=true;
                        printf("Case %d: %d\n",count,total1);
                    }
                }
            }
        }
        if(!flag)
            printf("Case %d: No answer\n",count);
    }
    return 0;
}

习题2-3 倒三角形 (triangle)

输入正整数n≤20,输出一个n层的倒三角形。例如,n=5时输出如下:

#########
 #######
  #####
   ###
    #

#include <cstdio>
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
        for(int o=0;o<i;o++)
            printf(" ");
        for(int p=0;p<2*(n-i)-1;p++)
            printf("#");
        for(int q=0;q<i;q++)
            printf(" ");
        printf("\n");
    }
    return 0;
}

习题2-4 子序列的和 (subsequence)

输入两个正整数n<m<10^6,输出 1 n + 1 ( n + 1 ) 2 + . . . + 1 m \frac{1}{n}+\frac{1}{(n+1)^2}+...+\frac{1}{m} n1+(n+1)21+...+m1,保留5位小数。输入包含多组数据, 结束标记为n=m=0。
提示:本题有陷阱。

样例输入:

2 4
65536 655360
0 0

样例输出:

Case 1: 0.42361
Case 2: 0.00001
#include <cstdio>
int main()
{
    int n,m;
    int count=0;
    while(scanf("%d %d",&n,&m)==2&&(n!=0||m!=0))
    {
        count++;
        double sum=0;
        for(int i=n;i<=m;i++)
        {
            double term=1.0/i/i;//i*i会溢出
            sum+=term;
        }
        printf("Case %d: %.5lf\n",count,sum);
    }
    return 0;
}

习题2-5 分数化小数 (decimal)

输入正整数a,b,c,输出a/b的小数形式,精确到小数点后c位。a,b≤10^6,c≤100。输入包含多组数据,结束标记为a=b=c=0。
样例输入:

1 6 4
0 0 0

样例输出:

Case 1: 0.1667

这里我们注意到描述“精确到小数点后c位”及“c≤100”,这就说明了C++自带类型的精度不足以满足题目要求,我们可以采用数组存储并模拟手算除法满足题目要求。

#include <cstdio>
int main()
{
    int m[105];//整数部分m[0] 小数部分m[i](1<=i<=101)
    int a,b,c,count=0;//被除数a 除数b 精度c
    while(scanf("%d %d %d",&a,&b,&c)==3&&(a!=0||b!=0||c!=0))
    {
        count++;
        for(int i=0;i<=c;i++)
        {
            m[i]=a/b;
            a=a%b;
            a*=10;
        }
        if(a/b>=5)//第c+1位
        {
            for(int i=c;i>=0;i--)
            {
                m[i]+=1;
                if(m[i]<10)
                    break;
                else
                    m[i]-=10;
            }
        }
        printf("Case %d: %d.",count,m[0]);
        for(int i=1;i<=c;i++)
            printf("%d",m[i]);
        printf("\n");
    }
    return 0;
}

习题2-6 排列 (permutation)

用1,2,3,…,9组成3个三位数abc,def和ghi,每个数字恰好使用一次,要求abc:def:ghi=1:2:3。按照“abc def ghi”的格式输出所有解,每行一个解。
提示:不必太动脑筋。

这里采用了C++流将三个整型整合为字符串型,通过“是否有9个不同的字符数字”这一条件来判断解。理论上用sprintf也可以实现。

#include <iostream>
#include <cstring>
#include <sstream>
using namespace std;
int main()
{
    string s;
    stringstream ss;
    for(int abc=123;abc<=329;abc++)
    {
        int def=abc*2;
        int ghi=abc*3;
        ss.str("");//清空内容(释放内存)
        ss.clear();//清空状态
        ss<<abc*1000000+def*1000+ghi;
        ss>>s;
        int i=0;
        char j='1';
        while('1'<=j&&j<='9')
            if(s.find(j++)+1)//下标转表项序号
                i++;
        if(i==9)
            cout<<abc<<' '<<def<<' '<<ghi<<endl;
    }
    return 0;
}

上面的解法重复使用了变量s和ss,这就需要使用ss.str("");ss.clear();来重置ss,其中:
ss.str("");是字符串流赋值函数,通过赋空串来清空该流。
ss.clear();是字符串流状态清空函数,人如其名。
当然也可以选择只用后者,但内存会爆掉,可能会MLE(Memory Limit Exceeded)。
更稳妥的办法是在需要重复使用同一个字符串流时把两句都写上(如上)或采用循环结束即销毁的局部变量(如下)。

#include <iostream>
#include <cstring>
#include <sstream>
using namespace std;
int main()
{
    for(int abc=123;abc<=329;abc++)
    {
        int def=abc*2;
        int ghi=abc*3;
        string s;
        stringstream ss;
        ss<<abc*1000000+def*1000+ghi;
        ss>>s;
        int i=0;
        char j='1';
        while('1'<=j&&j<='9')
            if(s.find(j++)+1)//下标转表项序号
                i++;
        if(i==9)
            cout<<abc<<' '<<def<<' '<<ghi<<endl;
    }
    return 0;
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值