K小数查询
题意:
给一个长度为 n 数列 A ,然后m个操作,有如下两种:
· 1 l r x,表示对i\(\in\)[l,r],令 \(A_i=min(A_i,x)\)
· 2 l r k,表示询问区间[l,r]中第 k 小的数。
输入描述:
第一行输入两个整数 \(n,m(1≤n,m≤8×10^4)\)。
接下来一行 n 个整数描述数组 \(A(1≤A_i≤n)\) 。
接下来 m 行每行描述一个操作,操作格式与题面中相同,保证\(1≤l≤r≤n,1≤k≤r−l+1,1≤x≤10^9\)
输出描述:
对于每组询问,输出一个整数表示答案。
官方题解:
考虑区间线段树套权值线段树求区间k小值的算法:对 \([1,n]\) 建线段树,每一个线段树节点 \([l,r]\) ,用一棵动态开点的权值线段树记录 \([l,r]\) 中每种权值出现了多少次。
如果能够维护这样的数据结构,询问就可以转化为在 \(O(logn)\) 棵权值线段树上二分,能在 \(O(lon^2n)\) 的时间里得到答案。
修改时,首先和普通的区间线段树一样,定位到某些节点[l,r]:这次修改相当于把内层线段树中所有大于等于 \(x\) 的数并到 \(x\) 的位置,这可以被转化成 \(t+1\) 次线段树单点修改操作,其中 \(t\) 为被并入的节点个数。把这 \(t+1\) 次修改应用到内层线段树 \([l,r]\) 以及 \([l,r]\) 所有祖先节点的内层线段树上。这一步的时间复杂度为 \(O(tlog^2n)\) 。
接着,在 \([l,r]\) 上打上对 \(x\) 取 \(min\) 的标记。在标记下传的时候,同样当前节点的内层线段树中,更大的节点的值并到 \(x\) 处,但是因为次数祖先节点的内层线段树的信息都是正确的,所以不需要把修改应用到祖先节点的内层线段树上。
时间复杂度和所有操作的 \(t\) 的和有关,不难发现,每一次操作结束后,内层线段树的叶子节点个数都减少了 ,而最开始一共有 \(O(nlogn)\) 个叶子节点,所以 \(t\) 的总和不会超过 \(O(nlogn)\) 。因此总的时间复杂度为 \(O(nlog^3n)\) 。
个人:
对于动态开点建权值线段树 练习参考luogu3939以及luogu1383
主要在于题解说 : 每一次操作结束后,内层线段树的叶子节点个数都减少了 ,而最开始一共有 \(O(nlogn)\) 个叶子节点,所以 \(t\) 的总和不会超过 \(O(nlogn)\)。所以复杂度没有看起来那么高。
比较难考虑到的是题解的up函数(就是上传内函数的修改)。
....好吧 整套思路还是难想 因为是二分答案+树套树... (加油(ง •_•)ง
【看着题解又又又胡搞了份难看的代码,然后神奇的过了
代码如下
#include
using namespace std;
const int maxn=3e7+5;
const int inf=0x3f3f3f3f;
int n;
int T[maxn],L[maxn],R[maxn],sum[maxn],tot;
int mi[maxn],up[maxn];
//对每个区间 动态开点 建权值线段树
inline void add(int&rt,int l,int r,int x,int v)
{
if(!rt)rt=++tot;
if(l==r)
{
sum[rt]+=v;return;
}
int mid=(l+r)>>1;
if(x<=mid)add(L[rt],l,mid,x,v);
else add(R[rt],mid+1,r,x,v);
sum[rt]=sum[L[rt]]+sum[R[rt]];
}
inline int get(int rt,int l,int r,int tl,int tr)
{
if(!rt)return 0;
if(tl<=l&&r<=tr)return sum[rt];
int mid=(l+r)>>1,res=0;
if(tl<=mid)res+=get(L[rt],l,mid,tl,tr);
if(tr>mid)res+=get(R[rt],mid+1,r,tl,tr);
return res;
}
inline void build(int k,int l,int r,int x,int v)
{
mi[k]=inf;
add(T[k],1,n,v,1);
up[k]=max(up[k],v);
if(l==r)return;
int mid=(l+r)>>1;
if(x<=mid)build(k<<1,l,mid,x,v);
else build(k<<1|1,mid+1,r,x,v);
}
/*
op==1: [l,r], Ai=min(Ai,x)
等效把[l,r]区间里的大于x的数 并入x的计数,并清零
*/
inline void down(int k)
{
int s;
if(mi[k]>1)upp(k>>1,x,num);
}
inline void clear(int rt,int k,int l,int r,int x,int num)
{
if(l==r)
{
sum[rt]-=num;
if(k>>1)clear(T[k>>1],k>>1,1,n,x,num);
return;
}
int mid=(l+r)>>1;
if(x<=mid)clear(L[rt],k,l,mid,x,num);
else clear(R[rt],k,mid+1,r,x,num);
sum[rt]=sum[L[rt]]+sum[R[rt]];
}
inline void update(int k,int l,int r,int tl,int tr,int x)
{
if(x>=up[k])return ;
if(tl<=l&&r<=tr)
{
int sum,num;
sum=get(T[k],1,n,x+1,up[k]);
if(sum)upp(k,x,sum);
for(int i=x+1;i<=up[k]&∑i++)
{
num=get(T[k],1,n,i,i);sum-=num;
if(num)clear(T[k],k,1,n,i,num);
}
up[k]=mi[k]=x;
return;
}
if(mi[k]!=inf)down(k);
int mid=(l+r)>>1;
if(tl<=mid)update(k<<1,l,mid,tl,tr,x);
if(tr>mid)update(k<<1|1,mid+1,r,tl,tr,x);
}
inline int query(int k,int l,int r,int tl,int tr,int x)
{
if(tl<=l&&r<=tr)
{
if(x>=up[k])return get(T[k],1,n,1,up[k]);
else return get(T[k],1,n,1,x);
}
if(mi[k]!=inf)down(k);
int mid=(l+r)>>1,res=0;
if(tl<=mid)res+=query(k<<1,l,mid,tl,tr,x);
if(tr>mid)res+=query(k<<1|1,mid+1,r,tl,tr,x);
return res;
}
int main()
{
int m,x,op,l,r,L,R,mid,t,res;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&x);
build(1,1,n,i,x);
}
while(m--)
{
scanf("%d%d%d%d",&op,&l,&r,&x);
if(op==1)update(1,1,n,l,r,x);
else
{
L=1,R=n,res=-1;
while(L<=R)
{
mid=(L+R)>>1;
t=query(1,1,n,l,r,mid);
// printf("#%d %d %d\n",mid,t,x);
if(t>=x)
{
res=mid;
R=mid-1;
}
else L=mid+1;
}
printf("%d\n",res);
}
}
}