[PAT Basic Level] 1045~1049

1045. 快速排序

题目分析:

这道题很有意思,我还是天真了一些,一开始的想法是:我先对这个序列进行排序,然后对排过序的序列与原始序列比较,那么只要发现两个数组中位置相同的元素值相同,那就发现了一个主元素,因为显然排过序的左边值都小于当前值,右边大于当前值,那么说明这个元素就在它按主元素划分后该在的位置。
但是,我想的太简单了!!!这也是我看了网上人家的分析才意识到。感谢博主DoctorLDQ的博客(大家可以点击链接去看原文),他的博客让我意识到了这种方法的缺陷。
为了直观,先举个例子吧:
{4,2,1,3,5},那么排序后是{1,2,3,4,5},显然2对应相等,但是在原序列中2不是主元素。
所以真正正确的判定方法还需要加上一条,即当前元素大于前面所有元素中的最大值。显然,如果满足的话,根据排序完毕的序列知,比当前元素大的序列大的是后面那些元素,不妨设是k个,而原始序列的当前元素已经比前面的都大,那么原始序列中,后面元素中至少包含了k个比当前大的元素,故后面的元素个数 ≥ \ge k,而后面一共就只剩k个,所以后面每一个元素都比当前大,就满足了主元素的条件。
注意:
也算一个坑,当主元素个数为0时,输出一个空行。。。PAT出题这些人真是奇葩,对格式明明有些奇怪要求,又不说,除了坑人还能有啥用?

源代码

#include <stdio.h>
#include <algorithm>
int main()
{
    int num;
    scanf("%d",&num);
    int *value=new int[num];
    int* cmp=new int [num];
    int *masterElem=new int[num];
    for(int i=0;i<num;++i){
        scanf("%d",&value[i]);
        cmp[i]=value[i];
    }
    std::sort(value,value+num);
    int count=0,max=-1;
    for(int i=0;i<num;++i){
        if(cmp[i]==value[i]&&cmp[i]>max){
             masterElem[count++]=cmp[i];
             max=cmp[i];
        }
        if(cmp[i]>max) max=cmp[i];
    }
    printf("%d\n",count);
    if(count){
        for(int i=0;i<count;++i){
            if(i) printf(" ");
            printf("%d",masterElem[i]);
        }
    }
    printf("\n");
    delete []value;
    delete []cmp;
    delete []masterElem;
    return 0;
}

1046. 划拳

题目分析:

相对容易的一道题,就规规矩矩读数,求和,然后判定哪个猜对了就好。另外为了稍微简单点,当二者猜的数相同时,直接继续就好,不必进一步比较了。

源代码

int main()
{
    int  num;
    scanf("%d",&num);
    int callA,gestA,callB,gestB;
    int countA=0,countB=0;
    while (num--){
        scanf("%d %d %d %d",&callA,&gestA,&callB,&gestB);
        if(gestA==gestB) continue;
        else if(gestA==callA+callB) //A猜对了
            countB++;
        else if(gestB==callA+callB) //B猜对了
            countA++;
    }
    printf("%d %d",countA,countB);
    return 0;
}

1047. 编程团体赛

题目分析:

这题也比较简单,为了方便,可以直接开一个1001的数组,用队伍编号作为数组的下标,这样读取和修改数据就非常方便了。

源代码

#include <stdio.h>

int main()
{
    int num;
    scanf("%d",&num);
    int team[1001];
    for(int i=0;i<1001;++i) team[i]=0;
    int teamID,member,score;
    while(num--){
        scanf("%d",&teamID); getchar(); scanf("%d",&member);//读取但不用
        scanf("%d",&score);
        team[teamID]+=score;
    }
    int max=-1;
    for(int i=1;i<1001;++i){
        if(max<team[i]){
            max=team[i];
            teamID=i;
        }
    }
    printf("%d %d",teamID,max);
}

1048 数字加密

题目分析:

规律倒是给的不复杂,但是又藏了一些特殊情况让你去猜。。
注意:

  1. 若A的长度小于B:这种情况是从样例中已经给出来了,不难分析其对应方法。这时候把A缺少的那些位视作为0,继续按照奇偶位计算法则对应求解即可。
  2. 若B的长度小于A: 这种情况你不说就有点坑吧,正常思路难道不应该是我进来多少位出去多少位的吗?题目实际上要求即使你的待加密串没那么长你也补到和A一样长,缺失的数字按0来统计。无奈。。
  3. 另外还得注意,对于B长度小于A时,别觉得补0后,0-A一定为负数,因为歹毒的出题人还可能把A的高位也设置成0。。。

明确了这些出题人的恶意之后,仔细编写就没什么问题了。不过编完之后,我也渐渐觉得开始的时候把这个数组逆置过来处理会方便很多,尤其是需要补位的时候,但是已经写完了,也不是很复杂,就不改了。

源代码

#include <stdio.h>

int main()
{
    int A[101],B[101];
    char output[13];
    for(int i=0;i<10;++i) output[i]=i+'0';
    output[10]='J';output[11]='Q';output[12]='K';
    int countA=0;
    while((A[countA]=getchar())!=' ') {A[countA]-='0';countA++;}
    int countB=0;
    while((B[countB]=getchar())!=EOF&&B[countB]!='\n'){
        B[countB]-='0'; //转换成十进制数
        countB++;
    } 
    bool flag=true; //奇偶标志位,奇数为true
    countA--; //丢弃空格
    int size=countB; //记录countB的数值
    while(countB--){
        if(countA<=-1) continue; 
        else{
            if(flag){ //若为奇数
                B[countB]+=A[countA];
                B[countB]=B[countB]%13;
                countA--;
                flag=false; //置为偶数标记
            }
            else{
                B[countB]-=A[countA];
                if(B[countB]<0) B[countB]+=10;
                countA--;
                flag=true; //置为奇数位标记
            }
        }
    }
    if(countA>-1){
        int* tmp=new int[countA+1];
        int count=countA+1;
        while(countA>-1){
            if(flag){//为奇数位
                tmp[countA]=A[countA];
                flag=false;
            }
            else{ //为偶数情况,由于此时0-A可能为0,为负则直接取负+10即可
                if(A[countA]==0) tmp[countA]=0;
                else tmp[countA]=10-A[countA];
                flag=true;
            }
            countA--;
        }   
        for(int i=0;i<count;++i)
            printf("%c",output[tmp[i]]);
        delete[] tmp;
    }
   
    for(int i=0;i<size;++i)
        printf("%c",output[B[i]]);
    return 0;
}

1049. 数列的片段和

挺有意思的一道题,题目给的数据可能有点独特的,所以要注意。这里我介绍两种方法,第一种是我自己想出来的,但是有一个测点2未通过,后来参考网上的另一种方法,修改了一下代码,就过了。不过其实这两种方但从数学上我感觉都没有问题,可能是前者在计算过程种存在一些舍入误差的原因?具体我也没分析出来。

法1:

最开始,我们观察这个加法,可以发现有这样一种规律:
对于某一个片段,不妨取3个元素来算吧{ a 3 , a 2 , a 1 a_3,a_2,a_1 a3,a2,a1},以 a 3 a_3 a3为首构成的所有片段, a 3 a_3 a3出现了3次, a 2 a_2 a2出现2次, a 1 a_1 a1出现1次。(注意,我没有说所有片段,而是强调了是以 a 3 a_3 a3为首的所有片段
那么在这个序列增加一个元素,变成{ a 4 , a 3 , a 2 , a 1 a_4,a_3,a_2,a_1 a4,a3,a2,a1},,那么以 a 4 a_4 a4为首构成的所有片段中, a 4 a_4 a4出现4次, a 3 a_3 a3出现3次, a 2 a_2 a2出现2次, a 1 a_1 a1出现1次。可以发现,实际上这时候它产生的片段的和,等于上一次元素产生的所有片段之和加上当前新增的元素乘以当前序列元素总数,对这个例子,也就是4。
从上面的规律,我们已经可以总结出来一个算法:采用一个变量pre储存上一个元素为首,产生的的所有片段和,用addNew变量储存当前元素产生的所有片段之和。然后从后往前逐个添加数,来计算所有片段的和。这些变量间的关系可以用下面的代码表示::
a d d N e w = p r e + c u r r e n t E l e m × c u r r e n t S i z e s u m + = a d d N e w p r e = a d d N e w addNew=pre+currentElem\times currentSize\\ sum+=addNew\\ pre=addNew addNew=pre+currentElem×currentSizesum+=addNewpre=addNew
那么我们从最后一个元素开始,逐步往前移,一直回溯遍历完整个输入序列,sum得到的就是整个序列能产生的所有片段和。

源代码1(测试点2不过)

int main()
{
    int num;
    scanf("%d",&num);
    double sum=0,newAdd,pre=0; //sum记录总和,newAdd记录当前元素所有片段之和,pre记录上一个元素所有片段之和
    double *arr=new double[num];
    for(int i=0;i<num;++i)
        scanf("%lf",&arr[i]);
    for(int i=num-1;i>-1;--i){
        newAdd=pre+arr[i]*(num-i);
        sum+=newAdd;
        pre=newAdd;
    }
    printf("%.2f",sum);
    delete []arr;
    return 0;
}
法2:

这也是网上很多人采用的方法,最好自己拿笔比划比划列一列。这个方法是从另一个角度考虑,容易发现对于序列 a 1 , a 2 , a 3 . . . . . . a n a_1,a_2,a_3......a_n a1,a2,a3......an
a 1 a_1 a1出现 n n n次,(在以 a 1 a_1 a1为首构成的片段出现 n n n次)
a 2 a_2 a2出现 ( n − 1 ) + ( n − 1 ) = 2 ( n − 1 ) (n-1)+(n-1)=2(n-1) (n1)+(n1)=2(n1)次(在以 a 1 a_1 a1为首片段出现 n − 1 n-1 n1次,以 a 2 a_2 a2为首的片段出现 n − 1 n-1 n1次)
a 3 a_3 a3出现 ( n − 2 ) − ( n − 2 ) + ( n − 2 ) = 3 ( n − 2 ) (n-2)-(n-2)+(n-2)=3(n-2) (n2)(n2)+(n2)=3(n2)次( a 1 a_1 a1为首的片段中 n − 2 n-2 n2次, a 2 a_2 a2为首的片段中出现 n − 2 n-2 n2次, a 3 a_3 a3为首的出现 n − 3 n-3 n3次)


. a k a_k ak出现 ( n − k + 1 ) + ( n − k + 1 ) + . . . + ( n − k + 1 ) = k ( n − k + 1 ) (n-k+1)+(n-k+1)+...+(n-k+1)=k(n-k+1) (nk+1)+(nk+1)+...+(nk+1)=k(nk+1)


a n a_n an出现 1 + 1 + . . . + 1 = n ∗ ( n − n + 1 ) 1+1+...+1=n*(n-n+1) 1+1+...+1=n(nn+1)
那么就可以发现出元素 a i a_i ai( i = 1 , 2 , 3... i=1,2,3... i=1,2,3...)出现的次数等于 i × ( n − i + 1 ) i\times(n-i+1) i×(ni+1)
所以根据这个规律,可以设计出比之前方法空间效率更高的算法,每次读取一个数,然后用这个数值乘以其出现的次数,从而计算出这个数对片段和的贡献,一直把所有输入读完计算一遍即可。这样的空间复杂度是 O ( 1 ) O(1) O(1)

源代码(AC)

#include <stdio.h>

int main()
{
    int num;
    scanf("%d",&num);
    double sum=0,value;
    for(int i=0;i<num;++i){
        scanf("%lf",&value);
        sum+=value*(i+1)*(num-i);
    }
    printf("%.2f",sum);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值