【bzoj2989】【二进制分组】【主席树】数列

【描述】
给定一个长度为n的正整数数列a[i]。
定义2个位置的graze值为两者位置差与数值差的和,即graze(x,y)=|x-y|+|a[x]-a[y]|。
2种操作(k都是正整数):
1.Modify x k:将第x个数的值修改为k。
2.Query x k:询问有几个i满足graze(x,i)<=k。因为可持久化数据结构的流行,询问不仅要考虑当前数列,还要
考虑任意历史版本,即统计任意位置上出现过的任意数值与当前的a[x]的graze值<=k的对数。(某位置多次修改为 同样的数值,按多次统计)

【思路】

由于我们要考虑任意历史版本,所以题意实际上是:
1.加入一个点(pos,val)
2.询问graze(x,i)<=k的点数

经过简单坐标变换,问题变成:
1.加入一个点(val+pos,val-pos)
2.询问坐标平面一个矩形里点的数量

那么这就是一个经典的偏序问题,可以用cdq分治+主席树离线解决。
可是如果强制在线呢?这里需要用到一个强大的工具,二进制分组。它可以解决很多修改对询问的贡献独立,强制在线的问题。
二进制分组会把修改分为若干组,每组用一个数据结构维护。新加入一个修改时,会根据修改个数和前面的修改进行合并。查询时在每一组的数据结构里分别查询。举个例子:
如果当前有5个修改,我们会把这些修改分组为:(4)(1)
加入第6个修改:(4)(1)(1)->(4)(2)
加入第7个修改:(4)(2)(1)
加入第8个修改:(4)(2)(1)(1)->(8)
当我们需要合并两组修改时,暴力重构数据结构即可。
所以问题变成了:
1.初始时有一些点
2.支持在线查询一个矩形点的数量
这个问题我们可以把所有点按x排序,以y为下标加入主席树即可。询问就变成了一个区间查询。显然这样一个方法并不支持动态加入,但是使用二进制分组就可以避免动态加入这个问题。
时间复杂度 O ( n l o g 2 n ) O(nlog^2n) ,空间复杂度 O ( n l o g n ) O(nlogn)
注意回收空间,否则空间复杂度会降为 O ( n l o g 2 n ) O(nlog^2n)
二进制分组复杂度分析
对于每个修改,我们发现合并复杂度与当且修改个数的lowbit有关。设 f ( x ) = x l o g 2 x f(x)=xlog_2x ,那么时间复杂度就是:
i = 1 n f ( l o w b i t ( i ) ) \sum_{i=1}^n f(lowbit(i))
对于每个lowbit分别考虑有多少个数的lowbit是它:
i = 1 l o g 2 n f ( 2 i ) n 2 i + 1 \sum_{i=1}^{log_2n}f(2^i)*\frac{n}{2^{i+1}}
即:
i = 1 l o g 2 n 2 i i n 2 i + 1 \sum_{i=1}^{log_2n}2^i*i*\frac{n}{2^{i+1}}
上式小于:
i = 1 l o g 2 n l o g 2 n n 2 \sum_{i=1}^{log_2n}log_2n*\frac{n}{2}
所以合并时间复杂度上界就是 O ( n l o g 2 n ) O(nlog^2n) 。查询需要在log个数据结构里花费log的代价,所以询问的时间复杂度也是 O ( n l o g 2 n ) O(nlog^2n)
代码:

#include<bits/stdc++.h>
#define re register
#define mp make_pair
using namespace std;
const int N=2e5+5,lim=2e5,M=1e5;
inline int red(){
    int data=0;bool w=0;char ch=getchar();
    while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
    if(ch=='-') w=1,ch=getchar();
    while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
    return w?-data:data;
}
typedef pair<int,int>T;
int n,m,a[N],b,c;
int rt[19][N],ch[N*40|1][2],s[N*30|1],top=0,siz[N*40|1],num=0,tot=0;
vector<T>v[19];
bool vis[N*40|1];
inline int node(int p=0){
	int u=top?s[top--]:++tot;
	ch[u][0]=ch[p][0],
	ch[u][1]=ch[p][1],
	siz[u]=siz[p];vis[u]=0;
	return u;
}
void del(int p){
	if(vis[p]||!p)return;
	s[++top]=p;vis[p]=1;
	del(ch[p][0]);del(ch[p][1]);
}
int change(int p,int l,int r,int pos){
	int u=node(p);++siz[u];
	if(l==r)return u;int mid=(l+r)>>1;
	if(pos<=mid)ch[u][0]=change(ch[p][0],l,mid,pos);
	else ch[u][1]=change(ch[p][1],mid+1,r,pos);
	return u;
}
int query(int u,int v,int l,int r,int ql,int qr){
	if(ql<=l&&qr>=r)return siz[u]-siz[v];
	int mid=(l+r)>>1,ans=0;
	if(ql<=mid)ans+=query(ch[u][0],ch[v][0],l,mid,ql,qr);
	if(qr>mid)ans+=query(ch[u][1],ch[v][1],mid+1,r,ql,qr);
	return ans;
}
void rebuild(int p){int len=v[p].size();
	sort(v[p].begin(),v[p].end());
	for(int re i=0;i<len;++i)
		rt[p][i]=change(i?rt[p][i-1]:0,1,lim,v[p][i].second);
}
void insert(int x,int y){
	vector<T>t;t.clear();t.push_back(mp(x,y));
	while(num&&t.size()==v[num].size()){
		for(int re i=v[num].size()-1;~i;--i)
			t.push_back(v[num][i]),del(rt[num][i]);
		num--;
	}v[++num]=t;rebuild(num);
}
int query(int x,int y,int len){
	int ans=0;
	for(int re i=1;i<=num;i++){
		int R=lower_bound(v[i].begin(),v[i].end(),mp(x+len,lim))-v[i].begin()-1;
		int L=lower_bound(v[i].begin(),v[i].end(),mp(x-len,0))-v[i].begin()-1;
		ans+=query(rt[i][R],L>-1?rt[i][L]:0,1,lim,max(1,y-len),min(lim,y+len));
	}return ans;
}
char ss[9];
int main()
{
	n=red();m=red();
	for(int re i=1;i<=n;i++)a[i]=red(),insert(a[i]+i,a[i]-i+M);
	while(m--){
		scanf("%s",ss);
		switch(ss[0]){
			case 'M':{int pos=red(),w=red();a[pos]=w;insert(w+pos,w-pos+M);break;}
			case 'Q':{int pos=red();cout<<query(a[pos]+pos,a[pos]-pos+M,red())<<"\n";break;}
		}
	}
}
发布了106 篇原创文章 · 获赞 22 · 访问量 5472

猜你喜欢

转载自blog.csdn.net/weixin_44111457/article/details/102889114