题面:
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->firstwhile(L!=R){//暴力跳cnt+=cont;//处理元素个数sum-=val*cont;//减去该桶的和L=mp.erase(L);}sum+=cnt*x,mp[x]+=cnt;write(sum);