「Luogu P6749 『MdOI R3』Yoshino」
先膜拜一发 BF.
题目大意
给出一个序列,每次可以将一段区间赋值为一段公差为 \(1\) 的等差数列,或者查询这个序列的逆序对个数.
分析
如果您已经认真做了上面两件事,那么多少会对这题有点思路了.
(下面提到的 等差数列 如果没有特殊说明就是指公差为 \(1\) 的等差数列)
先考虑一次覆盖会产生的逆序对的个数:
首先可以知道在这样一个等差数列内部是不会有逆序对的.
那么产生的逆序对的个数就等于在等差数列前面的所有数大于等差数列中的数的个数的总和.后面同理.
动态计算这个东西自然就会想到用树套树去维护.
不过这里不是一个数,而是一个等差数列,那么就可以用到这里所提到的做法了,对于内层的权值线段树上不只要维护区间和(\(\sum\limits_{i=l}^{r}sum_i\))还需要维护一个等差数列乘区间内的每个数(\(\sum\limits_{i=l}^{r}[(i-l+1)sum_i]\)),这样就可以通过一些简单的计算得到一个添加一个等差数列所得的贡献了.
接下来考虑如何删掉一次覆盖.
可以发现这题其实只有区间覆盖这一个操作(逆序对个数是时时计算的),所以 ODT 在这题中的复杂度是正确的.一次覆盖最多只会增加 \(3\) 个区间,总区间数的上界是 \(n+3m\) 显然是可以的.所以每次只需要在 ODT 上把覆盖到的所有区间的贡献在树套树上减掉,再把这次覆盖的贡献加上就可以了.
这样就可以直接用树状数组套权值线段树以及一个 ODT 来维护了.(其实和 BF 的做法没啥区别)
不过,树状数组套线段树见多了也就没啥意思了,所以来分享一下线段树套线段树的做法.
对于外层的线段树上是区间覆盖的标记,考虑标记永久化,对于删掉的覆盖需要将这些标记也删掉,那么如果存在一个节点的上面是有覆盖标记的,那么在这个节点的祖先和子孙中是不可能有存在标记的节点,因为标记不能下传的原因,所以如果需要查询的节点时某个被覆盖的节点的子孙,那么需要直接在这个节点的标记上查询,也就是计算两个等差数列相连之后的逆序对个数,这个东西可以通过简单的画图和计算得出,代码如下:
int Calc(int a,int lena,int b,int lenb)//第一个等差数列的首项为 a,长度为 lena,第二个等差数列的首项为 b,长度为 lenb
{
if(a<=b)//如果 a 的首项小于 b,那么就把 a 中没有贡献的部分去掉,是的保证 a 的首项大于 b
{
lena-=(b-a+1);
a=b+1;
}
if(lena<=0)//如果减掉之后第一个等差数列不存在了,那么就删掉
{
return 0;
}
//一下内容建议自行画图理解
int f=min(a-b,lenb);
int l=min(a-b+lena-1,lenb);
int len=l-f+1;
return (f+l)*len/2+(lena-len)*lenb;
}
写出来常数可能有点大,需要一些卡常技巧:
对于内层的线段树需要用标记永久化,不然每次下传标记的时候需要重新开节点,会导致 MLE,对于外层的线段树修改时对于被完全覆盖的区间就不需要在对应的线段树上修改了,可以直接使用上面的这个 Calc
函数.
代码细节挺多的.
代码
#include<bits/stdc++.h>
#define REP(i,first,last) for(int i=first;i<=last;++i)
#define DOW(i,first,last) for(int i=first;i>=last;--i)
namespace IO
//快读模板
using namespace IO;
using namespace std;
const int TOP=3e4+2;
const int MAXN=3e4+5;
int n,m;
int arr[MAXN];
long long answer=0;
namespace InSGT//内层线段树与 P4062 基本相同
{
struct LazyTag
{
int add;
inline void Clean()
{
add=0;
}
}for_make;
inline LazyTag MakeTag(int add)
{
for_make.add=add;
return for_make;
}
struct SegmentTree
{
int len,lson,rson,sum;
long long sum_;
LazyTag tag;
}sgt[MAXN*1000];
int sgt_cnt=0;
#define LSON sgt[now].lson
#define RSON sgt[now].rson
#define MIDDLE ((left+right)>>1)
#define LEFT LSON,left,MIDDLE
#define RIGHT RSON,MIDDLE+1,right
#define NOW now_left,now_right
inline void PushUp(int now)
{
//因为是标记永久化,所以需要在 PushUp 的时候把当前节点的标记的贡献也加上
sgt[now].sum=sgt[LSON].sum+sgt[RSON].sum+sgt[now].tag.add*sgt[now].len;
sgt[now].sum_=sgt[RSON].sum_+1ll*sgt[RSON].sum*(sgt[now].len-sgt[RSON].len)+sgt[LSON].sum_+(sgt[now].len+1)*sgt[now].len/2*sgt[now].tag.add;
}
inline void Down(LazyTag tag,int &now,int left,int right)
{
sgt[now].tag.add+=tag.add;
sgt[now].sum+=sgt[now].len*tag.add;
sgt[now].sum_+=(sgt[now].len+1)*sgt[now].len/2*tag.add;
}
void UpdataAdd(int now_left,int now_right,int add,int &now,int left=1,int right=TOP)
{
if(now_right<left||right<now_left)
{
return;
}
if(!now)
{
sgt[now=++sgt_cnt].len=right-left+1;
}
if(now_left<=left&&right<=now_right)
{
Down(MakeTag(add),now,left,right);
return;
}
UpdataAdd(NOW,add,LEFT);
UpdataAdd(NOW,add,RIGHT);
PushUp(now);
}
int QuerySum(int now_left,int now_right,int now,int left=1,int right=TOP,int tag=0)//在查询的时候需要记录从根节点到当前节点一路上的标记的和
{
if(now_right<left||right<now_left)
{
return 0;
}
if(now_left<=left&&right<=now_right)
{
return sgt[now].sum+tag*(right-left+1);
}
return QuerySum(NOW,LEFT,tag+sgt[now].tag.add)+QuerySum(NOW,RIGHT,tag+sgt[now].tag.add);
}
int QuerySum_(int now_left,int now_right,int now,int left=1,int right=TOP,int tag=0)
{
if(now_right<left||right<now_left)
{
return 0;
}
if(now_left<=left&&right<=now_right)
{
int len=right-left+1;
return sgt[now].sum_+(sgt[now].sum+tag*len)*(left-now_left)+(len+1)*len/2*tag;
}
return QuerySum_(NOW,LEFT,tag+sgt[now].tag.add)+QuerySum_(NOW,RIGHT,tag+sgt[now].tag.add);
}
#undef LSON
#undef RSON
#undef MIDDLE
#undef LEFT
#undef RIGHT
#undef NOW
}
namespace OutSGT//外层线段树和 ODT
{
int Calc(int a,int lena,int b,int lenb)
{
if(a<=b)
{
lena-=(b-a+1);
a=b+1;
}
if(lena<=0)
{
return 0;
}
int f=min(a-b,lenb);
int l=min(a-b+lena-1,lenb);
int len=l-f+1;
return (f+l)*len/2+(lena-len)*lenb;
}
struct SegmentTree
{
int root,tag;
}sgt[MAXN*4];
#define LSON (now<<1)
#define RSON (now<<1|1)
#define MIDDLE ((left+right)>>1)
#define LEFT LSON,left,MIDDLE
#define RIGHT RSON,MIDDLE+1,right
#define NOW now_left,now_right
void Build(int now=1,int left=1,int right=n)
{
if(left==right)//对于叶节点相当于是覆盖,所以只需要打上标记
{
sgt[now].tag=arr[left];
return;
}
REP(i,left,right)
{
InSGT::UpdataAdd(arr[i],arr[i],1,sgt[now].root);
}
Build(LEFT);
Build(RIGHT);
}
void Cover(int now_left,int now_right,int val,int add,int now=1,int left=1,int right=n)
{
if(now_right<left||right<now_left)
{
return;
}
if(now_left<=left&&right<=now_right)//对于被完全覆盖的区间只需要打上标记
{
sgt[now].tag+=add*(val+left-now_left);
return;
}
InSGT::UpdataAdd(val+max(now_left,left)-now_left,val+min(now_right,right)-now_left,add,sgt[now].root);//在内存线段树上修改
Cover(NOW,val,add,LEFT);
Cover(NOW,val,add,RIGHT);
}
int QueryL(int now_left,int now_right,int val,int now=1,int left=1,int right=n)//查询前面大于的个数
{
if(now_left<=left)
{
return 0;
}
if(sgt[now].tag)
{
return Calc(sgt[now].tag,min(right,now_left-1)-left+1,val,now_right-now_left+1);
}
if(right<now_left)
{
return InSGT::QuerySum_(val+1,val+now_right-now_left,sgt[now].root)+InSGT::QuerySum(val+now_right-now_left+1,TOP,sgt[now].root)*(now_right-now_left+1);
}
return QueryL(NOW,val,LEFT)+QueryL(NOW,val,RIGHT);
}
int QueryR(int now_left,int now_right,int val,int now=1,int left=1,int right=n)//查询后面小于自己的个数
{
if(right<=now_right)
{
return 0;
}
if(sgt[now].tag)
{
return Calc(val,now_right-now_left+1,sgt[now].tag+max(left,now_right+1)-left,right-max(left,now_right+1)+1);
}
if(now_right<left)
{
return InSGT::QuerySum(1,val+now_right-now_left,sgt[now].root)*(now_right-now_left+1)-InSGT::QuerySum_(val,val+now_right-now_left,sgt[now].root);
}
return QueryR(NOW,val,LEFT)+QueryR(NOW,val,RIGHT);
}
#undef LSON
#undef RSON
#undef MIDDLE
#undef LEFT
#undef RIGHT
#undef NOW
struct Node//ODT 部分
{
int l,r;
mutable int val;
Node(int left=0,int right=-1,int v=0)
{
l=left;
r=right;
val=v;
}
bool operator <(const Node &b)const
{
return l<b.l;
}
};
set<Node>s;
#define IT set<Node>::iterator
IT Split(int pos)//分裂区间,ODT 基本操作
{
IT place=s.lower_bound(Node(pos));
if(place!=s.end()&&place->l==pos)
{
return place;
}
--place;
int left=place->l,right=place->r;
int val=place->val;
Cover(left,right,val,-1);
s.erase(place);
Cover(left,pos-1,val,1);
Cover(pos,right,val+pos-left,1);
s.insert(Node(left,pos-1,val));
return s.insert(Node(pos,right,val+pos-left)).first;
}
void Assign(int left,int right,int val)//区间覆盖部分
{
IT place_left=Split(left),place_right=Split(right+1);
while(1)
{
IT i=s.lower_bound(Node(left));
if(i->l==right+1)
{
break;
}
answer-=QueryL(i->l,i->r,i->val);
answer-=QueryR(i->l,i->r,i->val);
Cover(i->l,i->r,i->val,-1);
s.erase(i);
}
Cover(left,right,val,1);
answer+=QueryL(left,right,val);
answer+=QueryR(left,right,val);
s.insert(Node(left,right,val));
}
#undef IT
}
namespace ReversePair//最开始时直接树状数组计算逆序对
{
int tree[MAXN];
int LowBit(int now)
{
return now&-now;
}
void Add(int num)
{
for(int now=num;now<=TOP;now+=LowBit(now))
{
tree[now]++;
}
}
int Query(int num)
{
int result=0;
for(int now=num;now;now-=LowBit(now))
{
result+=tree[now];
}
return result;
}
void Calc()
{
REP(i,1,n)
{
Add(arr[i]);
answer+=i-Query(arr[i]);
}
}
}
int main()
{
Read(n,m);
REP(i,1,n)
{
Read(arr[i]);
OutSGT::s.insert(OutSGT::Node(i,i,arr[i]));
}
OutSGT::s.insert(OutSGT::Node(n+1,n+1,0));
OutSGT::Build();
ReversePair::Calc();
int opt,l,r,val;
REP(i,1,m)
{
Read(opt);
if(opt==1)
{
Read(l,r,val);
OutSGT::Assign(l,r,val);
}
if(opt==2)
{
Writeln(answer);
}
}
return 0;
}