第一阶段集训总结
索引
-
考试总结
-
知识总结
-
心得体会
考试总结
第一次考试
- T1
很水的一道模拟题,主要考察位运算的原理和 unsigned
的溢出规则。
考场上打了一份字符串高精交上去 A 了。
实际上保留 $k$ 位二进制无符号整数相当于直接对 $2^k$ 取模,写起来会比我的字符串高精简单很多。
- T2
很水的 dp ,依赖推出来的一个性质。
我的性质推出来了,但是当时无法证明其正确性。保险起见,我就放弃了进一步思考,转而打暴力。
下次要记得即使结论不能证明正确性,也要打出来,手搓几组数据或者是对拍一下。
- T3
人类智慧构造题。
有很多的做法,我赛时却一个也想不出来。
看来我还不是人类。
自认为比较好想的一个方法是 8 个一组循环重复。但是注意最后剩下的 $<8$ 个数可能无法满足要求。因此我的处理方案是把最后一组八个的和这些数合起来打表处理。
当然,这个方法稍显人类智慧的局限性。还有更厉害的基于哥德巴赫猜想的写法,代码量小一些,利用剩余系证明。
- T4
一个题意就很难懂的题目。
读懂题意就有 40pts ,但奈何本人文科不是很好,加上最后半个小时才开这道题,痛失暴力分。
正解是倍增做法,我这一方面比较薄弱,论倍增也就只会一个 LCA 板子和裸的 Sparse Table。 所以直到现在我都还没补这道题。
第二次考试
虽然题目更常规一些,但是死得更惨了。
- T1
一道很简单的计数题。我不明白为什么场上只有 5 个人 A 掉。
考虑到正难则反。对于每个顶点,分别记录与它连接的红边与蓝边的个数。
将这两个数相乘就得到了非纯色三角形的个数,将每个顶点的这个值加起来。
每个非纯色三角形都会被两个顶点计算,因此最后数量要除以 2 。
最后用总数减去这个数量。时间复杂度 $O(n ^ 2)$。
- T2
一道看着很吓人的题。我在评测的时候始终不明白为什么有这么多人过。
而我呢,我在场上连暴力都没打。
就这样被某些人拉开了 100 分。心痛啊。
最后发现正解就是我考场上推出来的一个结论。但因为我无法证明其正确性就放了。上场比赛也出过这种事。
再次提醒自己,记得对拍,对拍,对拍。
- T3
纯纯大%拟。
满怀希望的打了接近 3000b ,最后过不了大样例。喜提 25pts 。
正解是用 bfs 做的。
码力弱了。
- T4
把式子变个型得到数组的性质:差分递增。
然后是一个四维 dp 。
悲。
知识总结
第一阶段主要学习的是序列 dp (LIS&LCS)。
LCS
讲的比较少。
主要是求方案数及最小字典序。
方案数很简单,在转移时顺便求出即可。
最小字典序呢?倒序求一遍,然后挨个枚举每一位。
可以采用dfs。注意:仅适用于字符集较小的情况。
LIS
这就有话可说了。
- 方案数
转移时顺便求出即可。
- 最小字典序方案
与 LIS 一样。
只需要倒序 dp 再枚举每一位就好。
某种意义上,这甚至比 LCS 简单,因为只用考虑一个数列。
- $O(n \log n)$ 的高效解法
法1. 二分
设 last[i]
表示长度为 i 的子序列的最小结尾, len
表示当前找到的最长公共子序列长度。
正序遍历数列 a
中的每一个数。
对于每个 a[i]
:
if (a[i] > last[len]) { last[++len] = a[i]; } else { *lower_bound(last + 1, last + len + 1, a[i]) = a[i]; }
采用反证法,易证 last
单调递增。
法2. 树状数组
二分能干的,树状数组基本上都能干。
因此本人从来没用过二分写法。
树状数组除了维护前缀和外还能维护前缀最大值。
因此按元素大小安排每个元素在树状数组中的位置。
值域较小时可以直接以 a[i]
作下标,若 a[i]
值域过大则应先进行离散化。
这样,每次查询前缀最大值,查到的都是比 a[i]
小的 a[j]
中所对应的 dp[j]
最大值。
参考实现:
dp[1] = 1; modify(a[1], 1); for (int i = 2; i <= n; i++) { int lst = query(a[i] - 1); dp[i] = lst + 1; modify(a[i], dp[i]); mxlen = max(mxlen, dp[i]); }
树状数组同样可以附加求方案数。
朴素的方案数很容易在求长度时顺便转移,只需要将所有能够转移到此状态的前面的状态的方案数求一个总和就行了。
但是由于数据范围,我们需要在 $n \log n$ 下解决问题。
回归到数据结构本身的原理。每一个 c[i]
都管的是 i - lowbit(i)
这个区间。
对于每个 c[i]
,开一个 cnt[i]
表示这段区间内所有取最大值的 dp[i]
所对应的方案数总和。
更新时,考虑当前的 dp
值与原来最大值的关系,分类讨论更新。
query时,当算出全局最大值后,再重新扫一遍树状数组,将这段区间最大值等于全局最大值的所有方案数总和加起来。
void modify(int pos, pair<int, int> val) { while (pos <= n) { if (val.first > c[pos]) // 分类讨论:如果大于直接覆盖 { c[pos] = val.first; cnt[pos] = val.second; cnt[pos] %= MOD; } else if (val.first == c[pos]) // 如果等于就加上,保留原来的 { cnt[pos] += val.second; cnt[pos] %= MOD; } pos += lowbit(pos); } } pair<int, int> query(int pos) { int tmp = pos; pair<int, int> res = {0, 0}; while (pos > 0) { res.first = max(res.first, c[pos]); pos -= lowbit(pos); } pos = tmp; while (pos > 0) { if (c[pos] == res.first) { res.second += cnt[pos]; res.second %= MOD; } pos -= lowbit(pos); } return res; }
但是上面的算法是有缺陷的。当数列中存在相同的数时,它无法去重。
先考虑朴素的去重转移方程。
感觉不好说,上代码。
for (int i = 1; i <= n; i++) { memset(vis, 0, sizeof vis); dp[i] = 1; for (int j = 1; j < i; j++) if (a[j] < a[i]) dp[i] = max(dp[i], dp[j] + 1); for (int j = i - 1; j > 0; j--) { if (vis[j]) continue; vis[j] = true; if (dp[j] + 1 == dp[i] && a[j] < a[i]) { cnt[i] += cnt[j]; } if (dp[i] == 1) cnt[i] = 1; } }
也就是说,对于两个都可以转移且对应元素值相同的 dp[j]
,只加上最靠后的那一个。
转化成树状数组实现。我们需要在每次修改时将原来与其对应元素值相同的 cnt
先减去。
void modify(int pos, pair<int, int> val, int now) { int tmp = pos; while (pos <= n) { if (c[pos] == maxlen[a[now]]) { cnt[pos] -= maxcnt[a[now]]; } pos += lowbit(pos); } pos = tmp; while (pos <= n) { if (c[pos] < val.first) { c[pos] = val.first; cnt[pos] = val.second; cnt[pos] %= MOD; } else if (c[pos] == val.first) { cnt[pos] += val.second; cnt[pos] %= MOD; } pos += lowbit(pos); } }
query 完全一样。
此处使用了 maxcnt[]
与 maxlen[]
来记录以每个值结尾的最大长度及其对应方案数。在转移时顺便更新即可。
心得体会
-
理性的思维方式。
思考问题不要太感性。对于一个结论,应当给出严谨的证明。不要不明不白地过题。对于所有讲过的知识点,要明白其原理而非只搞清楚实现方式。
-
稳定的考场心态与正确的考试策略。
注重考试策略,每道题都要看。不要畏难。不要紧张,
坐和放宽。 -
合理利用时间。
每场考试完后尽快补题。珍惜自己的上机时间,做尽可能多的题目。
期待自己不久后能够取得更大的进步。
最后的最后,感谢你看到这里。
本文作者:aaaaaaqqqqqq
本文链接:https://www.cnblogs.com/aaaaaaqqqqqq/p/17976957
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步