CF1398E Two Types of Spells(平衡树)
一、关于 set
这题其实考场上想得差不多了,但是没时间写平衡树,又忘记 set 怎么用了,于是 gg,所以借此题总结一下 set 的用法。
- 基本操作:
se.insert(x); se.erase(x);
,\(x\) 是插入或删除的数值; - 两个布尔型(后者“可以看作”):判断是否为空
se.empty()
,判断元素 \(k\) 是否存在se.count(k)
; - 两个常用指针:
se.begin()
指头,加*
取最小值;se.rbegin()
指尾,加*
取最大值(注意不是end()
);
要取出第 \(k\) 大?似乎不行。虽然可以用 set<int>::iterator
暴力跑,但是复杂度显然崩坏,只能手写或者 pbds 了。还有一些 size() clear()
什么的,不再赘述。
二、思路
看这道题:对于一个时刻,显然的思路是统计出闪咒的个数 \(k\),让前 \(k\) 大翻倍。注意,最小的闪咒一定不会被翻倍。
运用反证法,如果它被翻倍,说明在它之前一定存在一个没有被翻倍的闪咒,则交换两者顺序能获得更优解 ——Wall_breaker
因为要支持 \(\mathcal{O(\log n)}\) 插入删除,所以要使用 set;因为要维护闪咒的个数和前 \(k\) 大,我们可以开三个 set:
- \(\text{Big}\) 维护当前前 \(k\) 大的咒语;
- \(\text{Small}\) 维护当前不是前 \(k\) 大且不是最小闪咒的咒语;
- \(\text{Power}\) 维护当前所有闪咒。
三、插入/删除
基本想法是先直接插直接删,最后再调整使其合法。
1.插入/删除
插入一个火咒 \(X\),直接扔进 \(\text{Small}\);
插入一个闪咒 \(X\),先和 \(\text{Power}\) 最小值 \(Y\) 比较一下,如果小于则说明 \(X\) 是最小闪咒,不能加入 \(\text{Small}\),反倒应该把 \(Y\) 插入;如果大于,则放心大胆加入 \(\text{Small}\),最后不要忘记在 \(\text{Power}\) 里也插入一个 \(X\)。
删除一个火咒 \(X\),在 \(\text{Big}\) 和 \(\text{Small}\) 里找一下,扔掉。
删除一个闪咒 \(X\),先和 \(\text{Power}\) 最小值 \(Y\) 比较一下,如果小于则说明 \(X\) 是最小闪咒,在 \(\text{Big}\) 和 \(\text{Small}\) 里找不到,不用管;如果大于,则在它俩里面找一下,扔掉,最后同样不要忘记删掉 \(\text{Power}\) 里的那个 \(X\)。
2.调整
如果 \(\text{Big}\) 元素个数少于 \(\vert \text{Power} \vert\)(即 \(\text{Power}\) 的元素个数),则从 \(\text{Small}\) 中拿最大值;如果多于 \(\vert \text{Power} \vert\),则把最小值扔进 \(\text{Small}\)。如果 \(\text{Big}\) 的最小值小于 \(\text{Small}\) 的最大值,则交换之。
到这里其实就基本做完了,统计答案可以开两个变量 \(sum,add\),\(sum\) 统计当前所有元素和,\(add\) 记录翻倍的元素和。前者可以每进来一个 \(d\) 就加一下,后者可以在每次修改 \(\text{Big,Small}\) 的时候记录。
下面是 AC 代码:
#define Max(x) (*x.rbegin())//返回集合元素最大/小值
#define Min(x) (*x.begin())
void main() {
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1,tp,d; i<=n; ++i) {
cin>>tp>>d;
//修改部分
sum+=d;
if(d>0) {//加入元素
if(tp==0) {//火咒
Small.insert(d);
} else {//闪咒
++Power_cnt;//记录闪咒个数,便于调整时比较
if(!Power.empty()) {
if(d>Min(Power)) Small.insert(d);
else Small.insert(Min(Power));
}
Power.insert(d);
}
} else {//删除元素
d=-d;
if(tp==0) {//火咒
if(Big.count(d)) {
Big.erase(d);
add-=d;
} else Small.erase(d);
} else {//闪咒
--Power_cnt;
Power.erase(d);
if(!Power.empty()) {
if(d>Min(Power)) {
if(Big.count(d)) {
add-=d;
Big.erase(d);
} else Small.erase(d);
} else {
if(Big.count(Min(Power))) {
add-=Min(Power);
Big.erase(Min(Power));
} else Small.erase(Min(Power));
}
}
}
}
//调整部分
while(Big.size()<Power_cnt&&!Small.empty()) {//少于|Power|
add+=Max(Small);
Big.insert(Max(Small));
Small.erase(Max(Small));
}
while(Big.size()>Power_cnt) {//多于|Power|
add-=(Min(Big));
Small.insert(Min(Big));
Big.erase(Min(Big));
}
while(!Big.empty()&&!Small.empty()) {//元素不合法
if(Min(Big)<Max(Small)) {
add+=Max(Small)-Min(Big);
Big.insert(Max(Small));
Small.erase(Max(Small));
Small.insert(Min(Big));
Big.erase(Min(Big));
} else break;
}
cout<<sum+add<<endl;
}
}