上机题目题解

利息计算

n=int(input())
t=int(input())
I=float(input())
ans=0
res=n
for i in range(t):
    ans+=res*I/100+n/t
    res-=n/t
print("%.2f"%ans)

按照题意直接计算即可

首先要注意利率可能是小数,所以int(input())读入I肯定会有问题。然后要用/而不是//

有一些同学是这么写的:

a=n/t
while n!=0:
    n-=a
    ......

因为这里使用的是浮点数的减法运算,Python的浮点运算是有误差的,所以最后n的结果可能会非常接近0但不是0.用while循环就会出现TLE的情况。在明确知道了循环次数后,尽量不要使用while循环,用for更好一些。

快速傅里叶变换

n,m=map(int,input().split())
a=list(map(int,input().split()))
b=list(map(int,input().split()))
for i in range(100):
    a.append(0)
    b.append(0)
c=[0 for i in range(300)]
for i in range(0,n+m-1):
    for j in range(i+1):
        c[i]+=a[j]*b[i-j]
    print(c[i],end=' ')

这道题很容易出现list越界的情况,所以我的代码直接在每个list后面添加了足够多的0避免这个情况发生。最后根据公式直接计算答案。

很多同学WA的原因都是因为list下标越界.解决的方法其实很简单粗暴:

for i in range(100):
    a.append(0)
    b.append(0)

在后面疯狂补0就可以了.不用恰好补类似n-m个0这样的,只要补的足够多就肯定没问题.

还有的同学的读入是这么写的:

a=list(input().split())
for i in range(len(a)):
    int(a[i]).......

这么写严格来说并没有错,但是很麻烦.当输入的数据很多时,每次使用list里的数据前都要通过int转换,会带来很大的时间消耗,数据很大时甚至可能导致TLE.所以,推荐的写法是这样:

a=list(map(int,input().split()))

一步到位,最简单省事.

变量命名

a=input()+'%'
l=[]
x=''
for i in a:
    if (i.isalpha()):
        x+=i#字母组成单词
    else:
        if (x!=''):
            l.append(x)#这个特殊字符前有单词
            x=''
        if (i.isdigit()):
            l.append(i)#数字直接存进去
if (len(l)==0):
    print('WaiBiBaBo')#全是特殊字符的情况
else:
    for i in l:
        if (len(i)==1 and i[0].isdigit()):
            print(i,end='')#输出数字
        else:
            print(i[0].upper(),end='')#首字母大写
            for j in range(1,len(i)):
                print(i[j].lower(),end='')#其他字母小写

这道题和上周的对话过滤有些像。大致的思路是,通过变量x记录单词,当检测到特殊字符时,说明之前的字母构成了一个单词,就把x放到list中,然后清空x方便下次记录。当发现是数字的时候也直接放到list里面。最后统一输出单词。

这道题有一些坑点,比如最后一个单词后面可能没有特殊字符,需要手动补充上一个(不然会漏掉一个单词)。还有就是首字母大写,其他字母小写的条件,首字母后面的字母是有可能也是大写的,不能直接输出,需要转换一下。很多同学90分PE都是这个原因.

二进制数密文系统

使用字典的解法:

n=int(input())
d={}
for i in range(2**n):
    s=bin(i)[2:]
    while (len(s)<n):
        s='0'+s
    d[s]=input()
a=input()
if (len(a)%n != 0):
    print('Type Error,Please Check!')
else:
    flag=0
    s=''
    ans=''
    for i in a:
        if (i!='0' and i!='1'):
            flag=-1
            break
        s+=i
        if (len(s)==n):
            ans+=d[s]+' '
            s=''
    if (flag==0):
        print(ans)
    else:
        print('Type Error,Please Check!')

这道题稍微复杂一些,拆开来看

输入部分:

n=int(input())
d={}
for i in range(2**n):
    s=bin(i)[2:]#去掉前面的0b
    while (len(s)<n):
        s='0'+s#手动补0
    d[s]=input()
a=input()

首先创建一个空字典(不使用题目中的函数也可以做,而且更简单一些),然后得到二进制,使用字符串切片去掉0b.这时遇到了第一个问题:前面少0.解决的思路很简单,发现长度小于n时,手动往前面补就行了.处理完毕后,在字典中构建一个二进制和明文的映射.预处理环节完毕.

转换部分:

a=input()
if (len(a)%n != 0):
    print('Type Error,Please Check!')
else:
    flag=0#合法性标记
    s=''#用来记录二进制数
    ans=''#最后的答案
    for i in a:
        if (i!='0' and i!='1'):
            flag=-1#不合法
            break
        s+=i
        if (len(s)==n):#当攒够n位二进制数后,可以转换了
            ans+=d[s]+' '#统计答案
            s=''#清空,继续转换
    if (flag==0):
        print(ans)
    else:
        print('Type Error,Please Check!')

最后的转换方法有很多,字符串切片直接转可以,一位位二进制累加到一起转也可以.这里采用的是后一种方法.

很多同学陷入了一个误区,二进制数的长度不一定是3.要根据输入数据判断.很多人WA在了这上面.

不使用字典的解法:

n=int(input())
l=[0 for i in range(200)]
for i in range(2**n):
    l[i]=input()
a=input()
ans=''
s=x=0
if (len(a)%n != 0):
    print('Type Error,Please Check!')
else:
    for i in a:
        if (i!='0' and i!='1'):
            print('Type Error,Please Check!')
            break
        x=x*2+int(i)
        s+=1
        if (s==n):
            ans+=l[x]+' '
            x=s=0
    else:
        print(ans)

这种写法仅供参考,不推荐同学们使用.思路是,明文存到list里,下标就是二进制数所对应的十进制数.需要进行的是把密文序列中的二进制转化为十进制.这里使用的是较为原始的方法,其实可以通过函数直接完成这一过程:

a=int('1010',2)

构造

n=int(input())
a='2'+'3'*(n-1)
print(a)

23333333333333333

技能加点

T=int(input())
while (T):
    T-=1
    n,k=map(int,input().split())
    a=list(map(int,input().split()))
    l=[0 for i in range(50)]#用来统计k进制每一位的值
    ans='YES'
    if (k==1):
        print('YES')
        continue#特殊情况直接处理
    for i in range(n):
        x=a[i]
        w=0#k进制的位数,从0开始计数
        while x:
            y=x%k#当前位的值
            if (y>0):
                l[w]+=1#不是0,统计
            if (l[w]>1 or y>1):#有多个数的k进制的同一位不为0,或者这个数的当前位不为1
                ans='NO'#不合法
            x//=k
            w+=1#k进制的下一位
    print(ans)

思路是,a_{i}对应的k进制数中,不能含有除0,1外的数.而且每一位上,至多有一个a_{i}的值为1.

数据有个坑点是,$k$可以为1.如果不处理,进制转换的while循环会TLE.

调试技巧分享

运行时出错

这部分内容刘泽群助教专门写了一篇详细的文章,见这里.推荐同学们仔细阅读.

然后再分享一篇文章:

python常见的报错提示

当本地运行时发生错误,无法正常运行代码时,需要通过Spyder右下角的报错提示,找到错误的类型,进而分析原因,进行修改.报错的信息可以在上面的文章中找到对应的说明和示例.

运行结果出错

这种情况最为常见.下面介绍几个常见的处理方法.

以这次实验第一题为例,有同学是通过while循环计算的:

a=n/t
while n>0:
    n-=a
    ......

然后本地运行一直不出结果,可以断定是发生了死循环的情况.但是理论上这种写法是不会出现死循环的,现在发生了,那就说明n的值有问题.我们希望找到n的值一直没有减到0的原因.这时可以在循环里加一句输出:

a=n/t
while n>0:
    n-=a
    print(n)
    ......

这样直接输出问题变量,通过观察输出结果,可以发现n好像没有真正减到0.于是就可以选择换个思路,把while替换成for.

这种输出变量的方法同样适用于本地运行结果WA的情况.当发现结果不对时,可以输出一下中间的计算数据,根据数据寻找出错的位置.

下面这篇文章讲解了如何使用Spyder自带的调试工具检查代码的运行情况.合理运用断点调试和变量监控,可以更加快速,方便地找出问题所在.希望同学们认真阅读下面这篇文章.

spyder 断点调试python代码

在期中期末考试时,助教的工作只有监考,不负责解答与代码相关的问题.所以,在平时一定要着重锻炼代码调试和查错能力,平时代码在本地运行出错后不要着急找助教询问,先尽可能自己尝试解决.

OJ上报错

png

以上图片仅供娱乐

在之前的三次周报中,都强调了本地能够通过样例是说明不了任何问题的.但是这次实验中还是有很多同学问为什么本地可以过,OJ不能过.这里再次说明一下,OJ上的数据会很全面,会有极限数据,也会有特殊性质的数据.这些数据的任务就是测出样例没测出来的漏洞.样例仅仅是一个参考,说明不了任何问题.

下面会分OJ上的错误类型讲解调试技巧.

WA

WA是最常见的情况,可能的原因非常多.发生这种情况后,首先要检查代码中的计算式,看有没有式子打错了,是否有变量未清零,输出格式是否正确.检查不能只用肉眼,需要自己手动造一些和样例不同的数据测试.

以这次实验第三题为例,假如发生了WA,而且自己看代码没有发现问题,就要通过数据测试.下面是一些测试的例子以及测这些数据的原因:

a++ #只有一个字母搭配特殊符号
++a #换一种形式
a2  #单个字母+单个数字
13a #换一种形式
AAA #纯字母,全大写

上面这些测试数据补充了样例中没有的几个类型.自己造数据时首要原则就是全面,尽可能构造所有合法的情况.如果都能顺利通过,再向助教求助(不过这时一般就能测出来问题了).

PE

PE发生的原因是多余的空格回车或是大小写错误.

还是以第三题为例,假如发生了PE,首先检查自己的输出格式,有没有多余的空格回车.如果没有,那就很有可能是大小写的问题.回过头仔细读一下题目描述中关于大小写的部分,发现不仅首字母要大写,后面的字母还需要小写.问题到这里就解决了.

PE发生后的解决流程:检查空格回车,和题目要求的格式比较->检查大小写,再仔细阅读一遍题目->基本所有的PE问题都能解决.

TLE

TLE发生的原因是时间复杂度错误或是死循环,偶尔会有常数过大的问题.

以第五题为例,很多同学交了一份枚举的代码,枚举10^{n}大小的数进行检查.发生TLE后,首先验算一下时间复杂度,当n取到最大值的时候,大约需要检查10^{100000}个数.学校的评测机不是神威·太湖之光,这么大的运算量1s内肯定出不来.这说明你的算法有问题,需要换一个思路解决.

第六题也有很多人TLE.验算时间复杂度:O(Tnlogn).代入最大的数据估算一下,1s内完全应该跑出来的.这时说明程序内部发生了死循环,需要着重检查while循环,思考while循环会在什么条件下发生死循环.分析后不难发现,k=1时会死循环.再看一眼数据范围:1 \leq k \leq 20.破案了.

当上述两种情况都不符合时,说明代码的常数比较大,需要压常数.

常见的手段包括:频繁使用的运算结果开一个变量存一下

for i in range(n):
    ans+=p/t*i
#改为
a=p/t
for i in range(n):
    ans+=a*i

频繁的类型转换要提前转换好.

for i in range(n):
    for j in range(m):
        ans+=int(a[i])*int(b[j])
#改为
for i in range(n):
    a[i]=int(a[i])
for j in range(m):
    b[j]=int(b[j])
for i in range(n):
    for j in range(m):
        ans+=a[i]*b[j]

TLE发生时的检查流程:验算时间复杂度,如果不对就换算法->再检查一下while循环,看是否计算式有误或漏了题目条件->都不符合就压一下常数

CE

这种情况发生时,100%是提交的时候忘了选语言,重新交一遍就可以解决.

在班群里问题的时候,推荐使用Ubuntu Paste.使用方法见第一周周报或是这里.因为有时助教仅仅根据你的代码截图可能不会一下就看出问题所在,又不可能对着截图一行行把代码抄下来运行,毕竟时间上不允许.如果能粘下来代码的话就会很方便.

洛谷题单

很多人在做题的时候感觉缺少思路,或是调试技巧不够熟练.有一个通用的解决方法:刷题.

之前给大家推荐的训练场因为一些原因被洛谷永久关闭了,取而代之的是题单功能.在这里推荐一下洛谷的题单:网页链接.如果时间充裕的话,最好从第一个题单开始往后刷.时间紧的话就优先刷刚刚学过的知识点对应的题目.

下面列举了课程内的知识点所对应的题单名称:

【入门1】顺序结构

【入门2】分支结构

【入门3】循环结构

【入门4】数组

【入门5】字符串

【入门6】函数与结构体

【算法1-2】排序(不需要做快速排序和归并排序部分)

【算法1-3】暴力枚举

【算法1-4】递推与递归

【算法1-5】贪心

【数学1】基础数学问题

【动态规划1】动态规划的引入

还有一些不在教学内容内,但是做了会对考试很有帮助的题单:

【算法1-1】模拟与高精度(高精度部分不需要做)

【算法1-7】搜索

【算法2-1】前缀和与差分 

题单中的题目不用全部做完,每一个知识点做代表性的几道题目就可以了.

说点什么
支持Markdown语法
在"Python第四周周报"已有2条评论
Loading...