CSP-S2024 题目解析
CSP-S2024 题目解析
T1
弱智题目,没什么好讲的了,直接桶排+扫一遍统计答案即可。
#include <iostream>
using namespace std;
#define N 100005
int n,a[N],tot[N],mn = 2e9,mx,ans;
signed main(){
cin >>n;
for (int i = 1;i <= n;i ++) cin >> a[i],tot[a[i]] ++,mn = min(mn,a[i]),mx = max(mx,a[i]);
int sum = tot[mn];
for (int i = mn + 1;i <= mx;i ++) {
if(!tot[i]) continue;
int t = min(sum,tot[i]);
sum -= t;
sum += tot[i];
ans += t;
}
cout << n - ans;
return 0;
}
T2
分类讨论+二分+最小点覆盖区间问题。
- \(a = 0\) 且 \(v_i>V\)
- 能捕捉到的摄像头 \([pos,m]\),\(pos\) 为第一个 \(p_k\) 能捕捉到其超速的摄像头编号。
- \(a>0\)
- 范围 \([pos,m].\)
- \(a < 0\)
- 范围 \([pos,x]\),\(x\) 为最后一个 \(p_k\) 能捕捉到其超速的摄像头编号。
然后我们就产生了 \(n\) 个区间,利用 最小点覆盖区间
解决 \(ans2\),\(ans1\) 可以通过 \(\mathcal{O}(n)\) 扫一遍解决。
T3
咱们不难想到一段一段的红蓝相间一定不劣于单个单个的红蓝相间。
因此设 \(f_{i,j}\) 表示当前到 \(i\),最靠近 \(a_i\) 且颜色与其不同的数是 \(j.\)
不难的转移:
- 若 \(a_i = a_{i-1}.\)
- \(f_{i,j}=f_{i-1,j}+a_i.\)
- 否则:
- \(f_{i,a_{i-1}} = \max_{k\in[1,i-1],j=a_k} f_{i-1,j}+[a_i=j]\times j.\)
考虑到每次转移只和 \(i-1\) 有关,直接删掉第一维。
得到 :
- 第一种情况:\(f_j=f_j+a_i.\)
- 第二种情况:\(f_{i-1}=f_j+[i=j]\times j.\)
因此空间复杂度为 \(\mathcal{O}(m)\) 的。
我们考虑优化。
由于第一种情况是 \(\mathcal{O}(1)\) 的,看看第二种情况的 \(\mathcal{O}(m).\)
对于 \([i=j]\times j\),实际上是 \(f_{i-1}=f_i+i\),这种情况一定是不劣于再到 \(i\) 前面找一个 \(a_k\) 使得 \(a_k=i.\)
那么其他的情况:其实就是 \(f_j.\)
那么我们对当前的 \(f\) 取个 \(max\) 不就行了,记为 \(\text{Max}.\)
第二种情况不难表达为 \(f_{i-1}=\max\{\text{Max},f_i+i\}.\)
因此两个都是 \(\mathcal{O}(1)\) 的,并且第一种情况可以用变量求出。
代码如下:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <stdlib.h>
#define int long long
#define N 200005
#define M 1000006
using namespace std;
int f[M],n,a[N];
signed main(){
int T;
cin >> T;
for (;T--;) {
cin >> n;
for (int i = 1;i <= n;i ++) cin >> a[i],f[a[i]] = -1e18;
int Max = 0,ans = 0;
for (int i = 2;i <= n;i ++) {
if (a[i] == a[i - 1]) ans += a[i];
else f[a[i - 1]] = max(Max,f[a[i]] + a[i]),Max = max(f[a[i - 1]],Max);
}
cout << Max + ans << endl;
}
return 0;
}
T4
我们遇到此题可以考虑一步一步慢慢思考,一开始就想线性如果 \(1h\) 没想出来是对信心产生了极大的打击的。
除了最暴力的暴力算法
时间复杂度 \(\mathcal{O}(Tnm\log n)\),期望可得 \(40\) 分
(赛场想到了类似的算法,但是最后没有调出来)。
不难想到从单个去看能否达到加入贡献是一种不错的实现,这是一种化总为单的思想。
记 \(p=c_i\),以及要补充的选手为未知选手。
对于每一个未知选手,为了让它有可能达到胜利的条件,都应该使其的能力值为 \(\text{max}.\)
我们用以下的方式讨论选手类型:
- \(i\leq p\),那么其为已知选手。
- \(i>p\),那么其为未知选手。
考虑模拟选手 \(i\) 成为胜利者(或不成为)的整个过程(下述过程中若有一处不满足条件,选手 \(i\) 将不能成为最后的赢家):
- 当 \(i\) 作为擂主,直接判断是否 \(a_i>R.\)
- 当 \(i\) 不作为擂主时,很难判断
(我就是因此没有调出来),要分类讨论:- 如果在这次比赛前的一次比赛的另一边胜者 \(v\) 不是未知选手,那么直接判断即可。
- 否则选手 \(i\) 直接失败。
代码有点难写,不过加上预处理这个时间复杂度是可观的,性价比较高。
优化暴力算法
时间复杂度 \(\mathcal{O}(T(n\log n+m))\) ,期望可得 \(76\) 分。
这样的单独处理有一些重复,为什么呢?因为 \(c_i\) 的答案符合单调性,若有 \(c_i<c_j\) 则在 \(c_j\) 中满足条件的选手 \(x(x\leq c_i)\) 必然可以在 \(c_i\) 中成为胜者。
不妨把整一个过程看成一颗二叉树,设 \(u\) 表示其根节点。
设 \(f_u,g_u\) 分别表示在 \(u\) 处的胜者以及想要一个未知选手成为 \(u\) 处的胜者,\(p\) 最大是多少。
记 \(depmax\) 为最多的轮数。
我们称一条从根节点一直往左子树走的路径为胜利路径,因为只有走到这个路径上的一点 \(u\),才表明能够在第 \(depmax-dep_u\) 轮成为胜利者。
转移请仔细认真理解。
我们考虑每次从 \(i\) 向上跳,维护一个 \(\max p\),跳到一个 \(u\):
- 选手 \(i\) 作为擂主,判断:
- \(a_i<R\),下播。
- 否则,判断:
- \(a_{brother_{fa_u}}\) 是否满足条件:
- 是,\(\max p\leftarrow \min\{p,g_{borther_{fa_u}}\}.\)
- \(a_{brother_{fa_u}}\) 是否满足条件:
若最终 \(i\) 走到胜利路径,则贡献一个范围(需要与选手类型区分,这是细节)与 \([1,\max p]\) 取交集,这个范围指的是在哪一些人之前(这就是个区间!)可以成为总场的胜利者。
发现是一个区间,考虑查分优化。
单组数据处理 \(\mathcal{O}(n)\),计算答案时间复杂度 \(\mathcal{O}(n\log n+m).\)
\(\log\) 消失之术——正解
时间复杂度 \(\mathcal{O}(T(n+m))\),期望得分 \(100\) 分。
按照原来的思路
我们发现一个重要的优化条件,即:进行加法的区间个数只有 \(\mathcal{O}(n)\) 个!
为什么呢?
因为作为在胜利路径上的点,这样的节点的子树深度恰好为 \([1,depmax]\) 各一个!而一个深度为 \(x\) 的胜利路径上的点 \(u\) 会有叶子结点数量这么多个区间贡献给它,而不难发现 \(\mathcal{O}(\sum_{k\in[1,depmax]} 2^k)=\mathcal{O}(n).\)
大胆一点,考虑对这 \(depmax\) 个子树全部遍历一次!
注意到:对于一个节点 \(i\) 有两点:
- 会不会其作为擂主直接痛失资格。
- 其对手能不能守擂成功,若可以向他的对手的 \(g\) 取最小值。
不难发现:后面的那个东西跟 \(a_i\) 没有关系。
而前者也是比较好处理的。
具体地,我们考虑将上述做法反转过来,自上而下地处理贡献。
考虑维护一个 \((x,y)\),经过一条边 \(u\rightarrow v\) 时,我们考虑枚举 \(i\) 从 \(v\rightarrow u\) 的过程:
- 如果 \(v\) 子树里面的点到 \(u\) 作为擂主,则将 \(x\) 向当前的 \(depmax\) 取 \(\max.\)
- 如果 \(v\) 中 \(u\) 不作为擂主,判断对面是否守擂成功,可以则将 \(u\) 向 \(g_{brother_v}\) 取 \(\min.\)
当我们到达了叶子结点的时候(也就是我们的 \(i\)),此时 \(x\) 就可以判断 \(i\) 在往上跳的时候能不能达到我们枚举胜利路径上的点。
而 \(y\) 就可以描述贡献区间的约束。
时间复杂度 \(\mathcal{O}(n+m)\),常数较大,很多细节,不好实现。