题解 P6749 【『MdOI R3』Yoshino】
放一下本题的官方题解吧。
其实这个题写正解代码难度相当的高。为了防止空间不够用的现象,我把本题空间改成了 500MB(不过由于我使用了一些优化,所以我的空间其实小。
对于有一些暴力能够跑过本题的情况,其实出题人根本就没有想到有这样的暴力,不过还是在这里向大家道歉。
以下是本题的官方题解。
Subtask 1
考虑暴力修改,暴力查询逆序对,复杂度
Subtask 2
将逆序对改用归并排序或权值树查询,复杂度
Subtask 3
由于修改长度是
Subtask 4
显而易见,在值域缩小的同时,修改区间也随之缩小。
于是我们可以考虑对每个值建一个树状数组,如果对应位置有这个值,则树状数组该位置的值就是
在修改时,对
注意不要忘了清楚修改区间内部对逆序对的贡献,这部分可以暴力。
由于值域可以认为是
Subtask 5
由于第两次操作一就会重置一次,我们可以考虑直接用数学方法计算出修改的贡献。
对于奇数次操作,直接重置,逆序对清零。
对于偶数次操作,分为
如果
则对于值在
而对于值在
如果
于是,我们就可以
Subtask 6
考虑颜色段均摊。
这是一种类似于珂朵莉树的 trick(事实上这就是珂朵莉树的推平操作),复杂度为均摊
对于一个区间染色的操作,我们可以直接将一个区间维护为一个点,并且将他装入在
在修改时,我们在
为什么这样做的复杂度是正确的呢?我们可以来证明一下。
最初的时候,每个数单独构成一个区间,所以一共有
而每次操作的时候,我们增加的区间包括断开左右边界区间时增加的两个区间,以及插入的新区间。
这样一来,整个操作过程中区间的总个数就是
而显然一个区间最多被插入一次,删除一次,且
本题中的操作一并不是颜色段均摊,但显然可以用类似的方法,均摊
但我们在修改的同时,还需要再维护一个动态逆序对,于是考虑令
考虑插入一个新段对逆序对的贡献(删除就将贡献减去)。
假设这个区间是
那么我们大致可以其他的数分为以下几类:
- 位于
[1,l-1] 区间且值小于x 的数。这类数没有贡献,直接无视。 - 位于
[r+1,n] 区间且值大于y 的数。这类数同样没有贡献,直接无视。 - 位于
[1,l-1] 区间且值大于y 的数。这类数对区间内的所有数都有贡献,只需统计个数并乘以r-l+1 。 - 位于
[r+1,n] 区间且值小于x 的数。这类数同样对区间内的所有数都有贡献,处理方法同上。 - 位于
[1,l-1] 区间且值位于[x,y] 之间的数。注意到,对于值为t(t\in[x,y]) 的数,它对这个区间的贡献为t-x ,因为这个区间内前t-x 个数的值会小于t-x 。于是我们可以对权值维护区间内的个数以及区间和,也就是一个范围内值在某个区间的数有几个,以及这几个数加起来是多少。然后用和减去个数乘以x 就是我们所要的结果。 - 位于
[r+1,n] 区间且值位于[x,y] 之间的数。处理方式类似于第五类数。
这样一来,我们就在修改的同时完成了对逆序对的维护,而复杂度没有发生变化。
于是最终的复杂度就是
总结
本题的难度并不高,没有涉及到很复杂的维护方法,也没有涉及到很高级的数据结构,主要还是对一些基本技巧的灵活应用。另外,有一个颜色段均摊的操作值得大家掌握。
另外,本题可能出现空间不够用的情况。在本题的 std 代码中,树套树中的内层线段树使用的是标记永久化的线段树,这也大大节省了本题的空间。出题人没有测试过下放的空间,但看到部分其他用户的提交记录,感觉下放的确空间大了好多。
贴一份代码吧,供大家参考。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
register int x=0;
register bool f=0;
register char c=getchar();
while(c<'0'||c>'9'){
if(c=='-') f=1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=(x<<3)+(x<<1)+c-48;
c=getchar();
}
return f?-x:x;
}
char cr[200];int ttt;
inline void print(register int x,register char k='\n') {
if(!x) putchar('0');
while(x) cr[++ttt]=x%10+'0',x/=10;
while(ttt) putchar(cr[ttt--]);
putchar(k);
}
inline void printl(register ll x,register char k='\n') {
if(!x) putchar('0');
while(x) cr[++ttt]=x%10+'0',x/=10;
while(ttt) putchar(cr[ttt--]);
putchar(k);
}
const int maxn=52233;
const int len=30000;
struct seg{
int v,ls,rs,tag;
ll sum;
}t[maxn*160];
struct ask{
int v;
ll sum;
friend ask operator +(ask a,ask b){
return (ask){a.v+b.v,a.sum+b.sum};
}
friend ask operator -(ask a,ask b){
return (ask){a.v-b.v,a.sum-b.sum};
}
};
int rt[maxn],m,n,tot,st[maxn*128],top,tem[maxn][2],cnt[2];
int nnd(){
if(top) return st[top--];
return ++tot;
}
int del(int o){
if(t[o].tag||t[o].sum||t[o].v||t[o].ls||t[o].rs) return o;
st[++top]=o;
return 0;
}
inline void change(int &o,register int l,register int r,register int ql,register int qr,register int v){
if(!o) o=nnd();
if(ql<=l&&r<=qr){
t[o].v+=v*(r-l+1);
t[o].sum+=v*(l+r)*(r-l+1)/2;
t[o].tag+=v;
o=del(o);
return;
}
int mid=l+r>>1;
if(ql<=mid) change(t[o].ls,l,mid,ql,qr,v);
if(qr>mid) change(t[o].rs,mid+1,r,ql,qr,v);
t[o].v=t[t[o].ls].v+t[t[o].rs].v+t[o].tag*(r-l+1);
t[o].sum=t[t[o].ls].sum+t[t[o].rs].sum+(ll)t[o].tag*(l+r)*(r-l+1)/2;
o=del(o);
}
inline void add(register int x,register int ql,register int qr,register int v){
for(register int i=x;i<=n;i+=(i&-i))
change(rt[i],1,len,ql,qr,v);
}
inline ask query(register int o,register int l,register int r,register int ql,register int qr,register ll tag){
if(ql<=l&&r<=qr){
ask res=(ask){0,0};
res.v=t[o].v+tag*(r-l+1);
res.sum=t[o].sum+tag*(r+l)*(r-l+1)/2;
return res;
}
int mid=l+r>>1;ask res=(ask){0,0};
if(ql<=mid) res=res+query(t[o].ls,l,mid,ql,qr,tag+t[o].tag);
if(qr>mid) res=res+query(t[o].rs,mid+1,r,ql,qr,tag+t[o].tag);
return res;
}
ask find(register int l,register int r,register int ql,register int qr){
if(ql>qr||l>r) return (ask){0,0};
if(l>r) return (ask){0,0};
ask res=(ask){0,0};
for(register int i=r;i;i-=(i&-i))
res=res+query(rt[i],1,len,ql,qr,0);
for(register int i=l-1;i;i-=(i&-i))
res=res-query(rt[i],1,len,ql,qr,0);
return res;
}
struct node{
int l,r,v;
friend bool operator <(node a,node b){
return a.l<b.l;
}
}tmp1,tmp2,tmp;
set<node> s;
set<node>::iterator it,it1,it2;
ll ans;
inline void split(register int x){
it=s.upper_bound((node){x,0,0});it--;
if(it->l==x) return;
tmp1=(node){it->l,x-1,it->v};
tmp2=(node){x,it->r,it->v+x-it->l};
add(it->l,it->v+x-it->l,it->v+it->r-it->l,-1);
add(x,it->v+x-it->l,it->v+it->r-it->l,1);
s.erase(*it);
s.insert(tmp1);s.insert(tmp2);
}
inline void update(register int l,register int r,register int x){
if(l!=1) split(l);
if(r!=n) split(r+1);
while(1){
tmp=*s.lower_bound((node){l,0,0});
if(tmp.l==r+1) break;
int tl=tmp.l,tr=tmp.r,vl=tmp.v,vr=tmp.v+tmp.r-tmp.l;
add(tl,vl,vr,-1);
ans-=find(1,tl-1,vr+1,len).v*(tr-tl+1)+find(tr+1,n,1,vl-1).v*(tr-tl+1);
ask res=find(1,tl-1,vl,vr);
ans-=res.sum-res.v*vl;
res=find(tr+1,n,vl,vr);
ans-=res.v*vr-res.sum;
s.erase(tmp);
}
s.insert((node){l,r,x});
add(l,x,x+r-l,1);
register int vr=r-l+x,vl=x;
ans+=find(1,l-1,vr+1,len).v*(r-l+1)+find(r+1,n,1,vl-1).v*(r-l+1);
ask res=find(1,l-1,vl,vr);
ans+=res.sum-res.v*vl;
res=find(r+1,n,vl,vr);
ans+=res.v*vr-res.sum;
}
signed main(){
n=read();m=read();
for(register int i=1;i<=n;i++){
int x=read();
s.insert((node){i,i,x});
add(i,x,x,1);
ans+=find(1,i-1,x+1,len).v;
}
s.insert((node){n+1,0,0});
for(register int i=1;i<=m;i++){
int opt=read();
if(opt==1){
int l=read(),r=read(),x=read();
update(l,r,x);
}
if(opt==2) printl(ans);
}
return 0;
}