C2第一次作业

小题

脑残了=.=

8(1分)
以下程序的执行结果是__.
int x = 1;
void func (int x);
main ()
{
func (x);
printf (“%d\n”, x);
}
void func (int x)
{
x = 3;
}

解析:1
func()修改的是函数内的局部变量x……

9(1分)
以下程序的运行结果是__.
struct n {
int x;
char c;
};
void func(struct n b)
{
b.x = 20;
b.c= ‘y’;
}
main()
{
struct n a = {10, ‘x’};
func(a);
printf (“%d,%c”, a.x, a.c);
}

解析:20,y
同上……

大题

子串逆置

【问题描述】
输入两行字符串s和t(s和t可以含空格,length(t)≤length(s)≤50),将s串中首次与t匹配的子串逆置,并将处理后的s串输出。
【输入形式】
输入文件为当前目录下的invertsub.in。 文件中有两行字符串s和t,分别以换行符作为结束符,其中换行符可能是Linux下的换行符,也可能是Windows下的换行符。
【输出形式】
输出文件为当前目录下的invertsub.out。 输出文件只有一行,包含一个串,为要求的输出结果。行末要输出一个回车符。
【输入样例】
helloworld
llowor
【输出样例】
herowollld
【时间限制】
1s
【空间限制】
65536KB
【上传文件】
上传c语言源程序,文件名为invertsub.c。

分析:很简单的一道题(然而某学弟runtime error了,哈哈哈哈哈,实例秀一发……)

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

#define N 60

char a[N], b[N], c[2 * N];

int main()
{
    char *p, tmp;
    int i = 0, j = 0;
    freopen("invertsub.in", "r", stdin);
    freopen("invertsub.out", "w", stdout);
    gets(a), gets(b);
    if((p = strstr(a, b)) == NULL){         //p为指向相同字符串起始位置的指针
        puts(a);
    }
    else{
        j = strlen(b) - 1;
        while(i <= j){          //交换
            tmp = *(p + i);
            *(p + i) = *(p + j);
            *(p + j) = tmp;
            i++, j--;
        }
        puts(a);
    }
    return 0;
}

区间

【问题描述】
给定n个闭区间[ai, bi](1 <= i <= n),这些区间的并可以表示为一些不相交的闭区间的并。要求在这些表示方式中找出包含不相交区间数目最少的方案。
【输入形式】
输入文件为当前目录下的prz.in。 该文件包含n行(3 <= n <= 50000),每行各包括两个以空格分隔的整数ai 和 bi,表示一个区间[ai, bi](1 <= ai <= bi <= 1000000)。
【输出形式】
输出文件为当前目录下的prz.out。 该文件内容为计算出来的不相交的区间。每一行都是对一个区间的描述,包括两个以空格分开的整数,分别为区间的下界和上界。 输出时将各区间按照升序排列输出。这里说两个区间[a, b]和[c, d]是按照升序排列的,是指a <= b < c <= d。
【输入样例】
5 6 1 4 10 10 6 9 8 10
【输出样例】
1 4 5 10
【时间限制】
1s
【空间限制】
65536KB
【上传文件】
上传c语言源程序,文件名为prz.c。

分析:两步——
1. 将每一个区间按照左端点递增顺序排列,eg:排序后为[1,4],[2,4],[2,6],[3,5],[3,6],[3,7],[6,8];
2. 从头开始找一个区间,算作已覆盖区域,在剩下的线段中找出所有左端点小于等于当前覆盖区域的右端点的线段中,右端点最大,的线段,加入已覆盖区域,直到最后。

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

#define N 50000

int interval[N + 10][2];

int cmp(const void *a, const void *b)               //根据区间左端点的大小对区间排序
{
    return ((int *)a)[0] - ((int *)b)[0];
}

void traverse(int n)                                //遍历所有区间
{
    int i, j;
    int left, right = interval[0][1];
    for(j = 0; j < n;){
        left = interval[j][0];
        right = (right > interval[j][1]) ? right : interval[j][1];
        if(interval[j+1][0] <= right){      //判断下一区间是否能和当前区间连续上
            for(i = 0; interval[j + i][0] <= right; i++){    //找到左端点在已覆盖区间内,的区间的最大的右节点
                right = (interval[j + i][1] > right) ? interval[i + j][1] : right;
                if(i + j >= n){
                    break;
                }
            }
            j += i;                      //如果能连续上,说明找了好几次才找到断开的区间,j要+i
            printf("%d %d\n", left, right);
        }
        else{
            printf("%d %d\n", left, right);
            j++;                        //如果不能连续上,说明找了一次就找到断开的区间,j++
        }
    }
}

int main()
{
    int n = 0, i = 0;
    freopen("prz.in", "r", stdin);
    freopen("prz.out", "w", stdout);

    while(scanf("%d%d", &interval[i][0], &interval[i][1]) != EOF ){
        n++, i++;
    }

    qsort(interval, n, sizeof(interval[0]), cmp);
    traverse(n);
    return 0;
}

兑换硬币

【问题描述】
写一个程序,从标准输入上读入一个正整数N(1 <= N <=1000),计算出N元人民币兑换成1分、2分和5分的硬币,有多少种可能的组合。将结果以整数的方式输出到标准输出上,占一行。
【输入形式】
正整数N。(1 <= N <=1000)
【输出形式】
整数。
【输入样例】
1
【输出样例】
541
【时间限制】
1s
【空间限制】
65536KB
【上传文件】
上传c语言源程序,文件名为nickle.c。

思路1:最简单的三重循环暴力求解三元一次方程 x+2y+5z=N 即可,但是有可能会超时!主体代码如下:

for(i=1;i<=N;i++)
{
    for(j=1;j<=N/2;j++)
    {
        for(k=1;k<=N/5;k++)
        {
            if(i*1+j*2+k*5==N)
            {
                n++;
            }
        }
    }
}

思路2:将三重循环优化为一重循环,即将三元一次方程优化为一元一次方程。
举例:以6分硬币为例分析如下
[情况1] 如果不用5分的硬币,则共有6/2+1种组合方法,这是因为:如果都用1分的硬币,只有1种方法;如果用上2分的硬币,最多可以有6/2个二分硬币,剩余的钱数都用一分的凑,只有一种情况。因此如果不用五分硬币,就共有6/2+1种组合方法。

[推广1] 这可以推广到n分硬币的情况,在不用5分硬币的情况,就可以有n/2+1种组合方法。

[情况2] 如果用五分硬币,如果用1个五分硬币,则将(6-5)分硬币按照情况1,即只有一分硬币和二分硬币,进行组合,组合的方法有(6-5)/2+1种。因为在此例中,最多只能用1个五分硬币,所以总共的组合方法就是((6-5)/2+1)+ 6/2+1=5种。

[推广2] 情况2推广到n分硬币的情况,如果只用1个五分硬币,将n-5分硬币按照情况1进行组合,方法有(n-5)/2+1种;如果用2个五分硬币,方法有(n-5*2)/2+1种;如果用3个五分硬币,方法有(n-5*3)/2+1种……因此,用上了五分硬币的情况总共有种。

[综上] 有公式 ((n5i)/2)+1 求和(i从0到n/5)

#include < stdio.h >

int  main()
{
    int n = 0, i = 0, times = 0;
    scanf( "%d", &n );
    for ( i = 0; i <= n / 5; i++ )
        times += ( ( n - 5 * i ) / 2 ) + 1;
    printf( "%d\n", times );
    return 0;
}

思路3:这道题其实是另一道DP问题的简化,只是其中的某一已知货币种类的特定情况。原题如下:

问题描述:
  母牛们不但创建了它们自己的政府而且选择了建立了自己的货币系统。由于它们特殊的思考方式,它们对货币的数值感到好奇。
  传统地,一个货币系统是由1,5,10,20 或 25,50, 和 100的单位面值组成的。
  母牛想知道有多少种不同的方法来用货币系统中的货币来构造一个确定的数值。
  举例来说, 使用一个货币系统 {1,2,5,10,…}产生 18单位面值的一些可能的方法是:18x1, 9x2, 8x2+2x1, 3x5+2+1,等等其它。
  写一个程序来计算有多少种方法用给定的货币系统来构造一定数量的面值。保证总数将会适合long long (C/C++) 和 Int64 (Free Pascal),即在0 到2^63-1之间。
输入:
  货币系统中货币的种类数目是 N (1<=N<=25)。要构造的数量钱是 T (1<= T<=10,000)。
  第一行: 二个整数,N 和 T 。
  第二行: 可用的货币的面值 。
输出:
  单独的一行包含那个可能的用这N种硬币凑足T单位货币的方案数。

思路:假设N种硬币面值依次为 v1,v2,vn ,用 f(t) 表示构造t的方案数。当 f(t)=0 时,表示不能用这些硬币组合出t。要构造出T,实际上相当于求解下面关于 xi 方程的非负整数解的个数:

v1x1+v2x2++vnxn=T
对于这个关于xi的多元方程的非负整数解的个数怎么求解,可以通过暴力搜索、回溯+分支限界,但是都不如DP简单。
DP如下:
不妨假设k是用 vn 构造T的最大的数,即 k=T/vn (eg:用2分构造9分最多使用 9/2=4 个2分硬币,这里k就是4),那么T可以按照下面的方式分解:
T=S0+Vn0
T=S1+Vn1
T=S2+Vn2
T=Sk+Vnk

  其中 S0Sk 不能凑整的余量,可由 v1,v2vn1 线性组合。相当于构造T的过程,分成用0, 1,2,……k个 vn 这k + 1种情况来考虑,所以可以得到下面的递推关系式。
   DP(n,t) 表示用前n种硬币,构造t的方案数,则:
DP(n,t)=DP(n1,tVn0)+DP(n1,tVn1)++DP(n1,tVnk)

  到这已经可以开始DP了 (空间复杂度为 O(NT) ,即用的是一个二维数组存储),又因为
DP(n,tVn)=DP(n1,tVn1)+DP(n1,tVn2)++DP(n1,tVnk)

  所以上面递推关系式等价于:
DP(n,t)=DP(n1,t)+DP(n,tVn)

  通过观察递推关系式,可以发现, DP(n,) 只会与 DP(n,) DP(n1,) ,所以在计算到 DP(n,) 时, DP(n2,) DP(n3,) ……的信息已经没有作用,没必要继续存储,所以就可以进一步优化算法的时间和空间复杂度 (空间复杂度为 O(T) ,即用一个一维数组存储)
  于是很简洁的DP代码出来了,只需要一个一维数组(刚刚没有优化的时候是二维数组)即可。

#include<stdio.h>

#define N 101000
#define TYPE 3          //硬币种类的数量

int main()
{
    int n, i, j;
    int type[TYPE] = {1, 2, 5};        //适用于不只有1、2、5三种硬币的情况
    int num[N] = {0};
    scanf("%d", &n);
    n *= 100;

    num[0] = 1;              //组合出0元只有一种方法:所有的硬币均为0

    for(i = 0; i < TYPE; i++){
        for(j = type[i]; j <= n; j++){
            num[j] += num[j - type[i]];
        }
    }
    printf("%d", num[n]);
    return 0;
}

实数格式识别

【问题描述】
合法的实数书写格式分一般格式和科学格式两种。分别描述如下:
一般格式为常见的书写格式,分为整数部分和小数部分两部分,中间分用小数点.分隔。整数部分最开始可能含有正号或负号,之后为不含前导零的数字串;小数部分是由0-9十种字符组成的任意长的字符串。当小数部分为0时,小数部分和小数点可以省略。
科学格式由系数部分和指数部分两部分组成,中间用英文字母E分隔。系数部分为实数书写的一般格式;指数部分为可带有正负号数字串。
例如,+2、-1.56为一般格式的实数,而6.2E-2、-9E8为科学格式的实数。
只有小数点而没有小数部分的书写格式为不合法,例如,23.,23.E16均为不合法的实数书写格式。
编程分析哪些数的书写是正确的,是用哪种方式书写的。
【输入形式】
输入文件为当前目录下的real.in。 该文件包含一个字符串(长度不超过20个字符),以回车符结束,表示一个数据(无多余空格)。
【输出形式】
输出文件为当前目录下的real.out。 该文件有一行。如果输入数据的书写是非法的,输出Wrong;如果输入数据是用一般格式书写的,输出“Format1”;如果该数据是用科学格式书写的,输出“Format2”。输出的末尾均要以一个回车符作为结束。
【输入样例1】
+1.23
【输出样例1】
Format1
【输入样例2】
-5.1.1
【输出样例2】
Wrong
【输入样例3】
-5.1E-2
【输出样例3】
Format2
【时间限制】
1s
【空间限制】
65536KB
【上传文件】
上传c语言源程序,文件名为real.c。

思路:画出流程图,一步一步判断就行了。但是会导致if...else...比较多。比较类似于编译原理中语法分析的流程。
完整流程:
BEGIN -> +/- ?(可有可无) -> 整数?(可终止:整数) -> 小数点? -> 整数?(可终止:小数) -> E? -> +/- ?(可有可无) -> 整数? -> END
以上流程一旦出错即可终止。

#include<stdio.h>

#define N 25

char a[N];
int sub = 0, flag = 0;          //sub:数组下标;flag:标记是否存在E
int Point_Num = 0, E_Num = 0;   //'.'和'E'的出现次数,防止类似5.1.1,1.1E1E1的情况

enum state{
    Symbol,
    Integer,
    Point,
    E,
    End,
    Unknown
}cur, next;

enum state getToken()
{
    if(a[sub] == '+' || a[sub] == '-'){
        sub++;
        return Symbol;
    }
    else if(a[sub] == '.'){
        sub++;
        if(++Point_Num > 1){        //防止5.1.1的出现
            return Unknown;
        }
        return Point;
    }
    else if(a[sub] == 'E'){
        sub++;
        flag = 1;                   //标记E的存在
        if(++E_Num > 1){
            return Unknown;
        }
        return E;
    }
    else if(a[sub] == '\0'){
        return End;
    }
    else if(a[sub] >= '0' + 0 && a[sub] <= '0' + 9){
        while(a[sub] >= '0' + 0 && a[sub] <= '0' + 9){
            sub++;
        }
        return Integer;
    }
    else{
        return Unknown;
    }
}

int judgeFormat()               //定义各符号的衔接顺序
{
    cur = getToken();
    if(cur == Unknown || (cur != Integer && cur != Symbol)){    //第一个必须是Symbol或者Integer
        return -1;              //注意是&&而不能是||
    }

    while(cur != End){

        next = getToken();
        if(next == Unknown){
            return -1;
        }

        if(cur == Symbol || cur == Point){
            if(next != Integer){
                return -1;
            }
            cur = next;
        }
        else if(cur == E){
            if(next != Symbol && next != Integer){
                return -1;
            }
            cur = next;
        }
        else if(cur == Integer){
            if(next == Symbol){
                return -1;
            }
            cur = next;
        }
    }
    return 1;
}

int main()
{
    freopen("real.in", "r", stdin);
    freopen("real.out", "w", stdout);
    gets(a);

    (judgeFormat() == -1) ? ( puts("Wrong") ) : ( (flag == 1) ? ( puts("Format2")) : ( puts("Format1") ) );
}

N!的分解

【问题描述】
将N!分解成素数幂的乘积。
【输入形式】
从标准输入读取一个整数N(1 <= N <= 30000)。
【输出形式】
结果打印到标准输出。 输出格式为:p1^k1*p2^k2…其中p1,p2…为质数且ki>1。当ki=1时只输出pi,ki=0的项不输出。分解式中的素数按从小到大输出。
【输入样例】
5
【输出样例】
2^3*3*5
【时间限制】
1s
【空间限制】
65536KB
【上传文件】
上传c语言源程序,文件名为decompose.c。

思路:使用筛法打质数表,然后对照质数表依次分解每一个因数。

#include<stdio.h>

#define N 30100

int prime[N], used[N];

void Sieve(int n)           //筛法,打质数表
{
    int i = 2, multi, index;
    prime[0] = prime[1] = -1;       //非质数标记为-1,质数标记为0
    while(i * i < n){
        for(multi = 2, index = i * multi; index <= n; multi++, index = i * multi){
            prime[index] = -1;
        }
        i++;
        while(i * i < n && prime[i] == -1){
            i++;
        }
    }
}

void calculate(int num)         //对照质数表,对num进行质数分解
{
    int i = 2;

    while(num != 1){
        if(num % i != 0){
            do{
                i++;
            }while(prime[i] == -1);
        }
        else{
            num /= i;
            prime[i] += 1;      //分解出来一个质数,相应的质数记录的次数+1
        }
    }
}

void print(int n)
{
    int i;
    if(prime[2] != 1){
        printf("2^%d", prime[2]);
    }
    else if(prime[2] == 1){
        printf("2");
    }
    for(i = 3; i <= n; i++){
        if(prime[i] > 1){
            printf("*%d^%d", i, prime[i]);
        }
        else if(prime[i] == 1){
            printf("*%d", i);
        }
    }
}

int main()
{
    int n, i;
    scanf("%d", &n);
    if(n == 1){
        return 0;
    }
    Sieve(n);
    for(i = n; i >= 2; i--){
        calculate(i);
    }

    print(n);
    return 0;
}

一个更简单的筛法的写法:

void Seive(int n)
{
    int i, j, k;
    notPrime[2] = 0;
    for(i = 2; i * i <= n; ++i){
        if(notPrime[i] == 0){
            j = k = i;
            while(j < n){
                j += k;
                notPrime[j] = 1;
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值