CSP S1 赛后重做

前言

如果没有退役,我就每天一篇博文!(一口毒奶

数数题漏解,代码阅读能力差。

还是不太甘心的吧,赛后AK一下,好歹证明一下自己是个OI选手,虽然其蒟无比。


一、选择题

题意:数字1,1,2,4,8,8所组成的不同的4位数的个数

反思:考场上以两种方式算都漏解了,于是弃疗。

重做:

本题的特殊性在于1和8这两个可重数字,故针对它进行讨论。

注意,此处讨论了1和8的个数之后,不可以用总排列数除以各个可重集排列数计算。因为讨论了1和8的个数之后,选择范围已经不是4种数任意选择。最好是如此:将固定下来的数集依次选座,再根据乘法原理得到最终解。

\(n\)为1的个数,\(m\)为8的个数。

  1. n = 0 , m = 1 不存在
  2. n = 0 , m = 2 C(4,2) * C(2,2) * 2! = 4 * 3 / 2 * 2 = 12
  3. n = 1 , m = 0 不存在
  4. n = 1 , m = 1 4! = 24
  5. n = 1 , m = 2 C(4,2) * C(2,1) * 2! = 24
  6. n = 2 , m = 0 同3. 12
  7. n = 2 , m = 1 同6.24
  8. n = 2 , m = 2 C(4,2) = 6

\(Ans = 12 \times 2 + 24 \times 3 + 6 = 24 + 72 + 6 = 102\)

题意

在数字0~9中,0/1/8颠倒过来看不变,9颠倒成为6,6颠倒成为9。某城市有五位车牌,问这些车牌中有多少车牌,上下左右颠倒之后仍然不变,且该五位数能被3整除。

题解

考虑分类讨论:

  1. 不含6或9,只含0/1/8的回文串

    分类讨论中心(特殊,计算总和时仅1次);剩余的讨论半边即可。

    中心为0,(0,0)1种,(1,8)可swap,2种

    中心为1,(8,8)1种 ,(0,1)可swap,2种

    中心为8,(1,1)1种,(0,8)可swap,2种

  2. 含(6,9)

    中心为0

    ​ (6,9)(0,0)两组间可调换位置,6,9间可调换位置 4种

    ​ (6,9)(6,9)4种

    中心为1

    ​ (6,9)(1,1)同理,4种

    中心为8

    ​ (6,9)(8,8)同理,4种

    \(Ans = 9 + 16 = 25\)

反思

感觉自己做数数题漏解情况严重。

最好是每一条都在前面写清楚分类依据,小心计数、排答案。


二、阅读程序

  1. while循环,令ans停留在i后面,第1个大于a[i]的位置前。

    由此反观上一条,当a[i] >= a[i - 1]时,ans维持原位,是因为a[i - 1] >= a[ans],则a[i]>= a[ans]也成立,只需向后移动指针。反之,如果a[i] < a[i - 1]时,指针必须从i开始。

    判断题:

    将if(i > 1&& a[i] < a[i - 1]) ans = i 改成if(i > 1&& a[i] != a[i - 1]) ans = i,结果是否出错

    考虑这句话的意义:指针重置为i。a[i] < a[i - 1]是a[i] != a[i - 1]的子集,结果当然不会出错,只是速度变慢。

    反思:

    考试的时候没有想清楚重置指针与保留指针的关系。实际上重置指针保证结果正确性,保留指针优化时间复杂度;那时好像仅以a[i] != a[i - 1]范围大于a[i] < a[i - 1],二者不等价,过程不同,就臆断答案不同。

    搞清楚程序实现与运算结果的关系,程序实现不一定影响结果

  2. 乍一看,并查集?求解每次连边的联通块大小乘积和?

    4)常规思路是cnt表示联通块大小,自然小于等于n。然而此处累加时没有判断同一联通块的情况!同一联通块累加,自然可能大于n。

    5)n = 50 , 连n - 1即49次边,每次的x,y不相同,意味着每一次都连接2个不同的联通块。

    每一次连边会减少1个联通块,最后成为1个联通块。

    如果一般情况不会推的话,用特殊情况求解:n个点,每次拿siz最大的联通块和下1个单点连接,则\(ans = \sum _{i = 1} ^ {n - 1} = (1 + n - 1) (n - 1) / 2 = \frac{n(n - 1)}{2} = \frac{n^2}{2} - \frac{n}{2}\)

    \(n = 50\)时,\(ans = 25 * 49 = 1225\)

    6)该并查集没有路径压缩不要主观臆断!不要按照自己的代码习惯猜测!因此复杂度是平方的。

  3. 考虑循环中变量含义(正常循环入手,先不看边界)

    除了i = 0的情况,tmp = pre[i - 1] 前i - 1个字符在t中的最大匹配数/终止匹配位置

    i,j是两个指向s的指针,暂且不管它是干什么的。

    当pre[i - 1] >= suf[j] + 1时,j后移。考虑循环终止的条件,pre[i - 1] < suf[j] + 1。

    考虑pre[i - 1] < suf[j] + 1的意义,i - 1的最后匹配位置为pre[i - 1] - 1 , j的最前匹配位置为suf[j] + 1。pre[i - 1] < suf[j] + 1等价于pre[i - 1] - 1 < suf[j] < suf[j] + 1。

    即i - 1的最后匹配位置与j的最前匹配位置至少隔了1个位置,无法与t完成匹配;

    也可以反过来说,pre[i - 1] >= suf[j] + 1时,pre[i - 1] - 1 >= suf[j] + 1 - 1。即i - 1的最后匹配位置至少与j的最前匹配位置相邻。因此,若在s中取子段[1,i - 1],那么在另一头取子段[i,j - 1]时,t都是这两者拼接起来的子段的子序列。假若规定了该拼接子段不能为与原串相同,则有j - i - 1种方案。

    判断题

    2)当t是s的子序列时,输出一定不为0。

    ​ 当t = s时,由于有非原串的性质,输出为0。故判错。

    ​ 反思:当时没有这么耐心(也没办法这么清醒地一点点地分析),凭感觉乱写的。

    4)当t是s的子序列时,pre数组和suf数组满足:对于任意0 <= i < slen,pre[i] > suf[i + 1] + 1

    ​ 当t是s的子序列时,suf[i + 1] + 1<= pre[i] - 1 + 1 ,即 pre[i] >= suf[i + 1] + 1。故判错。

    5)若tlen = 10,输出为0,则slen最小为?

    ​ 本来以为是sb题,ans初始值就是0,那slen = 0就可以了。

    ​ 然而:看清楚,输入了1个字符串,你见过cin输入空串的吗程序实现中,除了理论,还要结合实际

    ​ 当slen = 1时,1 - 0 - 1 = 0,符合题意。

    6)若tlen = 10 , 输出为2,则slen最小为?

    ​ 理想情况,s为t中间插入2个字符。slen >= 12。


    三、补全程序

    技巧:如果自己能想到解法,百分百没错。如果想不到,可以试着复读。

    T1比较水,直接口胡解法;考场上根本没有办法冷静下来想T2,时间上或许也不允许(以我没半小时推不出dp的速度)。本质上还是算法能力不足。

    本蒟蒻就把T2当完整的博弈dp来写好了。

    题意

    Alice和Bob在取石子,一开始有m颗石子。有n条规则,第i条规则为,当石子的个数大于等于a[i]并且大于等于b[i]时,可以取走b[i]个石子。当某人无法依照已有的任一规则,则该人输。询问先手是否必胜。

    \(1 \leq n \leq 64 , a_i,m \leq 10^7,b_i \leq 64\)

    题解

    \(f[i]\)表示剩余i颗石子,先手是否必胜。用1表示先手必胜,0表示先手必败。

    枚举所有规则\(j\),若存在\(j\)满足\(i \geq a[j] ,i \geq b[j] , f[i- b[j]] = 0\),则\(f[i] = 0\)

    注意到\(b[j]\)小于等于64,\(f[i]\)只需通过\(f[i - 64 \rightarrow i - 1]\)转移,可将这些状态值用1个64位二进制数存储。

    反思

    考试时大概知道正常dp怎么做,但没有手动写方程。看到64位变量status和trans瞬间蒙圈,一直瞎琢磨为什么可以不用状态值f[i],以为是什么本蒟蒻想不通的高大上操作。

    实践出真知才是程序填空题的正解!沉下心来先把自己的正解写好,再适应别人的写法!

    回看

    注意输入下标从\(0\)\(n - 1\)

    第1段的意义在于将各条规则按照a[i]从小到大排序。

    看到status和trans,并且j单向移动,而不是每次从头来。结合dp流程和status和trans的英文含义,大胆猜测status表示从i - 1到i - 64的所有状态值,trans表示当前可以取的b[i]有哪些种类。

    当然最后发现反了也没有关系,再调转一次就可以了。因此第一次放心大胆地按照设想做,不要犹豫

    先看第2空,i从小到大,i >= a[j] && i >= b[j],所以自然要筛选出a[j] <= i的所有规则。a[j] < i的之前一定已经筛出,故本次筛出a[j] == i的即可。(考场上没认真回去看题,选成了小于...

    当时还有个疑惑,a[j]和b[j]大小关系又不确定,可能a[j] <= i但b[j] > i呢,这个不就不能选了吗。

    实际上,横竖b[j] <= 64,记到trans里面,求f[i]时不考虑b[j] > i的规则就可以了。

    因此第3空其实不用思考,二进制求并集,当然是用|啦,选A即可。

    并且从中可以得到某些信息,trans中,第1到64位对应b[i]从1到64。

    从输出win来看,win即f[i]。我们发现第1,4,5空都跟status中的0/1的含义有关。

    再次使用假设法。发现无法填空,再改也不迟啊。

    假设status中0表示必败态,1表示必胜态。则初始状态下, status = 0,不假思索地选A。

    再看第4处,win = 1,需要status的某位为0,trans的某位为1。可以考虑将status取反再用&处理。即为

    win = ~status & trans。第4题选D。

    再看第5处,status = status << 1 | win。第5题选D。

​ 重做答卷:ABADD 标准答卷:CBADD

​ 再反思

往后想的时候,不要忘记了前面定下的规则!前面的规则至少要在trans/status中得到体现。

​ 由于status中的0位为有效位,我们对其限制,不将其与默认值0混淆。故第1题选C。


结语

走出考场就感觉自己很凉。其实这次初赛是我有史以来准备得最认真的一次,毕竟高二了。

恶补各种计算机理论常识(搞了半天队友跟我说考的比例很小);买洛谷网校的课;做历年的题目。

但是真正考起来还是不行。一个是实力问题,比如数数题漏解;一个是技巧问题,比如在考场上没有自己把dp式子列一遍,导致最后1题完全不会做,赛后发现其实不难;还有考试的心态问题,本来自己思维不算敏捷,平时机试时思路都是慢慢出来的,所以在时间更有限的笔试中太不冷静。

归结起来,CSP-S1 2020,努力的方向就是算法能力 + 冷静

posted @ 2019-10-20 22:00  littlewyy  阅读(181)  评论(0编辑  收藏  举报