好像整体二分跑得飞快,不过蒟蒻是来练树套树的。才不是我不会任何离线算法
操作涉及区间插入、区间查询k小值。先想到了权值线段树套平衡树,平衡树存储位置。
查询时(假设查询区间 $[l,r]$ 的第k名)从权值线段树根节点出发,查询右儿子平衡树的$r$的排名-($l-1$)的排名,也就是右边区间中在区间 $[l,r]$的数的个数,比$k$小,就让$k$减去它,跳到左儿子上;反之跳到右儿子上。(比较像主席树区间$k$大值)
插入暴力将每个位置挨个插到平衡树中。
复杂度 $O(nlog^{2}n+len*log^{2}n)$ $\text{(len为总插入数字的个数)}$
$T$了$10$个点。$len$非常大。瓶颈就在于插入。一个个插入确实太慢了,有没有什么能快速把一段连续区间加进去?
线段树可以。权值线段树里套一个维护位置的线段树。插入直接区间加 $1$,查询区间求和,时间都是$logn$的。为了保证空间,还要动态开点。
时间复杂度:每次插入查询都要访问权值线段树的$logn$个节点,每个节点要访问内部的线段树的$logn$个节点。总复杂度$O(nlog^{2}n)$。
空间复杂度:每次插入都要访问权值线段树的$logn$个节点,每个节点内部的线段树最多会新开$logn$个节点,总复杂度$O(nlog^{2}n)$。
不过毕竟树套树常数大,容易 $T$ 。搞个标记永久化能优化不少。不过蒟蒻并不会卡常,程序自带大常数(比如AC自动机永远跑不进 $9000ms$),标记永久化还是 $T$ 了一个点。加了$fread$、快输才能不开$O2$过。
上代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#define maxn 50005
#define inf 0x3f3f3f
#define pn putchar('\n')
#define px(x) putchar(x)
#define ps putchar(' ')
#define pd puts("======================")
#define pj puts("++++++++++++++++++++++")
#define getchar() (*head++)
using namespace std;
char buf[1<<23],*head=buf;//fread优化
inline int read(){
int x=0,y=0;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')y=1;ch=getchar();}
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return y?-x:x;
}
template<typename T>
inline T read(){
T x=0;
int y=0;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')y=1;ch=getchar();}
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return y?-x:x;
}//快读
void put(int x){
if(x<0)px('-'),x=-x;
if(x>9)put(x/10);
px(x%10+48);
}//快输
int n;
#define ls(x) ls[x]
#define rs(x) rs[x]
struct Position_Segment_Tree{
long long dat[maxn*400];
int tag[maxn*400],ls[maxn*400],rs[maxn*400],cnt;
void add(int L,int R,int l,int r,int &node){
if(!node)node=++cnt;//动态开点
dat[node]+=(long long)(min(R,r)-max(L,l)+1);//标记永久化
if(L<=l&&R>=r){
++tag[node];
return;
}
int mid=l+r>>1;
if(L<=mid)add(L,R,l,mid,ls(node));
if(R>mid)add(L,R,mid+1,r,rs(node));
}
long long ask(int L,int R,int l,int r,int node,long long t=0){
if(!node)return (long long)(min(R,r)-max(L,l)+1)*t;
if(L<=l&&R>=r)return dat[node]+1ll*(min(R,r)-max(L,l)+1)*t;
int mid=l+r>>1;
long long ans=0;
if(L<=mid)ans=ask(L,R,l,mid,ls(node),t+tag[node]);
if(R>mid)ans+=ask(L,R,mid+1,r,rs(node),t+tag[node]);
return ans;
}
}pst;
#undef ls
#undef rs
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
struct Data_Segment_Tree{
int root[maxn<<3];
void insert(int poi,int L,int R){
int l=-n,r=n,node=1;
while(1){
pst.add(L,R,1,n,root[node]);
if(l==r)return;
int mid=l+r>>1;
if(poi<=mid)node=ls(node),r=mid;
else node=rs(node),l=mid+1;
}
}//为了卡常把递归改成了非递归的。不过好像没什么效果。。。
void ask(int L,int R,long long k){
int l=-n,r=n,node=1;
while(l<r){
int mid=l+r>>1;
long long sum=pst.ask(L,R,1,n,root[rs(node)]);
if(sum>=k)node=rs(node),l=mid+1;
else k-=sum,node=ls(node),r=mid;
}
put(l),pn;
}
}dst;
int main(){
fread(buf,1,1<<23,stdin);
n=read();
int m=read();
while(m--){
int s=read(),l=read(),r=read();
if(s==1){
int d=read();
dst.insert(d,l,r);
}
else {
long long k=read<long long>();
dst.ask(l,r,k);
}
}
}