题解:NFLSOI#31351. 小吃
题面:
Shor 小鸭已经准备了 \(n\) 个小吃盘子,准备一边看电影一边享用!第 \(i\) 个盘子最初装有一个美味值为 \(a[i]\) 的小吃。
你需要处理 \(q\) 个查询。在第 \(j\) 个查询中,Shor 将按顺序执行以下两个操作:
- 1. 吃掉所有美味值在 \(l[j]\) 到 \(r[j]\) (包含)之间的小吃。
- 2. 然后,将每一个被吃掉的小吃替换为一个美味值为 \(x[j]\) 的新小吃。
在处理任何查询之前,以及每次处理完查询之后,Shor 都希望你确定所有盘子中小吃的美味值总和。
形式化地说,给定一个长度为 \(n\) 的数组 \(a\),你必须处理 \(q\) 个查询。在处理所有查询之前,打印 \(a\) 中所有元素的总和。在第 \(j\) 个查询中,将所有满足 \(l[j]\le a[i]\le r[j]\) 的元素 \(a[i]\) 更新为 \(x[j]\),然后打印更新后的 \(a\) 中所有元素的总和。
输入格式
输入的第一行包含两个用空格分隔的整数 \(n\) 和 \(q\)。
第二行包含 \(n\) 个用空格分隔的整数 \(a[1],a[2],\dots,a[n]\)。
接下来的 \(q\) 行输入中,每一行包含三个用空格分隔的整数。第 \(j\) 行包含 \(l[j],r[j]\) 和 \(x[j]\),描述了第 \(j\) 个查询。
输出格式
输出应包含 \(q+1\) 行。
输出的第一行应包含一个整数,表示在所有查询之前 \(a\) 中所有元素的总和。
接下来的 \(q\) 行中,第 \(i\) 行应包含一个整数,表示第 \(i\) 个查询后 \(a\) 中所有元素的总和。
对于所有测试用例,输入将满足以下约束条件:
\(1\le n\le200000\);
\(0\le q\le200000\);
\(0\le a[i]\le 10^9\);
对于所有 \(1\le i\le n\),\(0\le x[j]\le 10^9\);
对于所有 \(1\le j\le q\),\(0\le l[j]\le r[j]\le 10^9\);
对于所有 \(1\le j\le q\)。
我们开始解题
首先我们考虑如何查找一个数组的满足值域的所有值,鉴于这道题的数值范围非常大,我们用普通的线段树去维护这个桶会爆炸,空间直接飞起来,我们就去考虑动态开点线段树,也就是动态分配当前线段树的子节点。
我这边简单地搓了一个,\(83pts\) 还是会 MLE。
我们发现这个动态开点的请求过多会造成冗余的点越来越多,但是内存并没有被释放,所以我们这里可以 delete 彻底一点。 delete 的我没有去调他(懒得调)所以就贴一个 \(83pts\) 的动开sgt的部分代码(而且还是伪代码)在这里。
struct Node{
int ls, rs;
int cnt;
int sum;
Node():ls(0),rs(0),cnt(0),sum(0){}
};
vector<Node>t;
int idx=0;
int n,q;
int sumall=0;
#define mid ((l+r)>>1)
#define lson t[u].ls
#define rson t[u].rs
void pushup(int u){
t[u].cnt=(!lson?0:t[lson].cnt)+(!rson?0:t[rson].cnt);
t[u].sum=(!lson?0:t[lson].sum)+(!rson?0:t[rson].sum);
}
void insert(int &u, int l, int r, int v, int k) {
if(!u)u=++idx,t.emplace_back();
if(l==r){
t[u].cnt+=k,t[u].sum+=v*k;
return;
}
if(v<=mid)insert(lson,l,mid,v,k);
else insert(rson,mid+1,r,v,k);
pushup(u);
}
void query(int u, int l, int r, int L, int R, int &cnt, int &sum) {
if(!u or R<l or L>r)return;
if(L<=l and r<=R){
cnt+=t[u].cnt;
sum+=t[u].sum;
return;
}
query(lson,l,mid,L,R,cnt,sum);
query(rson,mid+1,r,L,R,cnt,sum);
}
void del(int &u, int l, int r, int L, int R) {
if(!u or R<l or L>r)return;
if(L<=l and r<=R){
t[u].cnt=0,t[u].sum=0;
lson=rson=0;
return;
}
if(L<=mid)del(lson,l,mid,L,R);
if(mid<R) del(rson,mid+1,r,L,R);
pushup(u);
}
main(void){
t.reserve(N);//预留空间
t.emplace_back();//初始开空节点
read();
int root=0;
for(int i=0;i<n;i++)
read_and_insert();
write(sumall);
while(q--)
read;
int acnt=0,asum=0;
query(root,0,MAX_VAL,l,r,acnt,asum);
if(acnt>0)
del(root,0,MAX_VAL,l,r);
insert(root,0,MAX_VAL,x,acnt);
sumall=sumall-asum+x*acnt;
write(sumall);
更可行的正解
骗骗人的,因为这里有区间值域查询我们可能需要 \(\log\) 的时间来找到左右端点,然后去修改它,但是这个修改特别奇妙,是把整个区间全部推平,我们不难想到珂朵莉树,开平衡树去把这个点越缩越小。这里我们直接生搬硬套 \(\mathbb{STL}\) 的 map 来维护。
为什么平衡树直接暴力删点是可以的?,我们设这时候的数值种类有 \(n\) 种,要求改的区间大小为 \(len\):
然后非常非常地开心,(我绝对不会告诉你赛时我线段树调了一个多小时然后没AC),发现这个区间修改的时间总复杂度只有 \(\mathcal {O(n)}\) ,查询的时间复杂度 \(\mathcal {q\log n}\),这个 \(\log\) 还是收敛的。完全就是随便切这道题。
下面就可以给出这个代码了。我们一个 lower_bound 和 upper_bound 来快速查询左右端点,然后指针暴力往下跳就好,最后不要忘了这个 mp[x]=cnt。
for(int i=1,x;i<=n;i++)
read_ans_sum()
do_set();
write(sum);
for(int i=1;i<=q;i++)
read();
auto L=mp.lower_bound(l),R=mp.upper_bound(r);
#define cont L->second
#define val L->first
while(L!=R){//暴力跳
cnt+=cont;//处理元素个数
sum-=val*cont;//减去该桶的和
L=mp.erase(L);
}
sum+=cnt*x,mp[x]+=cnt;
write(sum);

浙公网安备 33010602011771号