CSP S1 赛后重做
前言
如果没有退役,我就每天一篇博文!(一口毒奶
数数题漏解,代码阅读能力差。
还是不太甘心的吧,赛后AK一下,好歹证明一下自己是个OI选手,虽然其蒟无比。
一、选择题
题意:数字1,1,2,4,8,8所组成的不同的4位数的个数
反思:考场上以两种方式算都漏解了,于是弃疗。
重做:
本题的特殊性在于1和8这两个可重数字,故针对它进行讨论。
注意,此处讨论了1和8的个数之后,不可以用总排列数除以各个可重集排列数计算。因为讨论了1和8的个数之后,选择范围已经不是4种数任意选择。最好是如此:将固定下来的数集依次选座,再根据乘法原理得到最终解。
设\(n\)为1的个数,\(m\)为8的个数。
- n = 0 , m = 1 不存在
- n = 0 , m = 2 C(4,2) * C(2,2) * 2! = 4 * 3 / 2 * 2 = 12
- n = 1 , m = 0 不存在
- n = 1 , m = 1 4! = 24
- n = 1 , m = 2 C(4,2) * C(2,1) * 2! = 24
- n = 2 , m = 0 同3. 12
- n = 2 , m = 1 同6.24
- 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整除。
题解
考虑分类讨论:
-
不含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种
-
含(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\)
反思
感觉自己做数数题漏解情况严重。
最好是每一条都在前面写清楚分类依据,小心计数、排答案。
二、阅读程序
-
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],二者不等价,过程不同,就臆断答案不同。
搞清楚程序实现与运算结果的关系,程序实现不一定影响结果。
-
乍一看,并查集?求解每次连边的联通块大小乘积和?
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)该并查集没有路径压缩!不要主观臆断!不要按照自己的代码习惯猜测!因此复杂度是平方的。
-
考虑循环中变量含义(正常循环入手,先不看边界)
除了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,努力的方向就是算法能力 + 冷静。