2021.09.16pm
| ---- | 预期 | 实际 |
|---|---|---|
| A | 100 | 50 |
| B | 100 | 0 |
| C | 50 | 50 |
| D | 100 | 90 |
| E | 100 | 100 |
| F | 100 | 50 |
| S | 550 | 340 |
可能水,一定菜
SPECIAL 赛车([CEOI 2003] The Race) \(\blacktriangle\!\blacktriangledown\!\blacktriangle\)
- 作为基础题的最后一道,我觉得有点委屈它。
- 这道题之前做过,并且做对了。但之前写的过于冗杂且效率不高。
- 这道题询问了两个问题,看似有联系,实则得分开求解,原因在于超车事件最多只输出 \(10000\) 个
Q1
- 第一问是相对容易解决的。会发生超车,是因为在后面的车跑的比你快,或者前面的车比你跑的慢。这不就是逆序对吗。用树状数组找就解决了。
- 但这里有一个小差别:用距离当第一关键词还是用速度当第一关键词。我们希望时间和空间尽量小,那就选范围大的作为第一关键词排序(同时距离也不会重复),时间复杂度和空间复杂度均从 \(O(n \log n+x \log x)\) 优化到 \(O(n \log n+v \log v)\)
Q2
- 定理一:\(A\) 超过 \(B\) ,则 \(B\) 不可能再超过 \(A\)
- 定理二: \(A\) 前方分别是 \(B\) 与 \(C\) 则,\(A\) 必须先超过 \(B\) 再超过 \(C\),或必须 \(B\) 超过 \(C\) 后 \(A\) 才可能超过 \(C\),\(or\) ,只能先超过相邻的人或被相邻的人超过
证明:容易证明(这里写不下)- 解这道题这两个定理就足够了。
- 我们先找每一个人 \(A\) 前方的第一个人 \(B\),如果能超车就放进优先队列,按照追上时间为第一关键字,追上位置为第二关键字降序排序(\(P.S.\) 某大佬想通过推导式子找出排序依据,并发出了 \(double\) 狗都不用的著名言论,
并在改成double后A了此题) - 每次\(push\)同时维护目前的位置状态,把新产生的两组可能追上的情况(\(A.B.C.D. \to A.C.B.D\),故可能 \(C\) 追上 \(A\) 和 可能 \(D\) 追上\(A\))
- 有两个小问题
- 一个是怎么堆外维护目前状态。
- 可以用链表(但是我写挂了,也和我的其他实现方式有关)
- 亦可以用映射。一个数组维护每辆车对应的距离排名,另一个数组维护总体的状态(以查找前后的车辆)
- 另一个是排重
- 为什么会有这个问题:\(A.B.C.D. \to A.B.D.C. \to B.A.D.C. \to B.D.A.C.\to D.B.A.C.\)
- 可以发现第二次和第三次\(D\) 与 \(B\) 都相邻,那么可能都会扔进堆里。
- 解决方法也很多,可以\(hash\),但是难写。题目中给出了不会在同一位置出现超车,我们第二关键字就是超车位置,那和前一个出堆的相同跳过答案就不会重复了
- 一个是怎么堆外维护目前状态。
A NOIP题海战 \(\blacktriangle\)
- 这道题主要是数据恶心。最后一行没给全,这样我的代码就会有问题。
- 就是道模拟,暴力都能过
B 有序表的最小和 \(\blacktriangle\!\blacktriangledown\)
- 维护一个堆,先把\(a[1]+b[i]\)全部放进去,每次取出时更新\(a[i] \to a[i+1]\) 再放进堆即可
C 木板 \(\blacktriangle\!\blacktriangledown\!\blacktriangle\)
这道题很高兴,因为只会打暴力DP- 严格复杂度 \(O(n^2)\) ,但这道题稍微优化一下能过掉
- \(DP\) 方程:
\[ DP[i]=\min(DP[i-1]+ch)
\]
- 表示已经拼了\(i-1\) 个木板,转移到拼 \(i\) 个木板,右端点最靠左是多少。(背包)
- 优化:先排序,从已知成立的位置开始向下以此枚举,枚举到第一个非最优的情况(再往下就更不可能是最优的)
- \(tag\) 是优先队列,但据说正解是贪心,
这究竟是什么题
D 区间和 \(\blacktriangle\)
- 树状数组
- 听说你没开 \(long long\)?(黑人抬棺)
E 堆排序 \(\blacktriangle\)
- \(RT\)
F 双端队列 \(\blacktriangle\!\blacktriangledown\)
- 之前做过,一遍对,复杂度\(O(n+n\log n)\),但是这次却写挂了···
- 我们可以把双端队列看成一个奇怪的队列。因为最后要拼成一个递增的数组,那么我们完全可以先按数值进行排序,从最小的开始以此选,看能否放进同一个双端队列,不能则再开一个
- 那么问题在于什么情况下必须新开一个双端队列?(我们先考虑没有重复元素的情况)
- 因为当前队列必须放入剩下的最小的元素,而放下之后就只能从一端进队列了,也就是说,位置在最小元素之前的用的是双端队列,之后的是队列。
- 上方虽然用的是双端队列,我们依然可以看成是队列,但加入顺序是从最小元素的位置先向上扫,再从最上面往下扫。也就是说,从当前最小的元素开始,依次加入大一点的元素,如果满足当前趋势(若在下侧,则只能一直向下;若在上侧,先向上扫再向下扫)则加入这个队列,不满足,则重新开一个。
- 对于相同元素,我们按照出现先后顺序进行排序,目的是能找到相同区间的左右端点,来判断目前趋势是否改变。
\(\cal CODE:\)
inline int suan(int fang){
int ans=1;
int maxn=0;
int i=1,j;
while(p[i+1].va==p[i].va)i++;//i,j是区间两个端点
maxn=p[i].num;
i++;
for(;i<=n;i++){
j=i;
while(p[j+1].va==p[i].va)j++;
if(fang==2){//向上
if(maxn>p[j].num)maxn=p[i].num;
else{
maxn=p[j].num;
fang^=1;
}
}
else{//趋势向下
if(maxn<p[i].num)maxn=p[j].num;
else{
ans++;
maxn=p[i].num;
fang^=1;
}
}
i=j;
}
return ans;
}

\(\cal {Made} \ {by} \ {YuGe}\)
浙公网安备 33010602011771号