【bzoj2989】【数列】【cdq分治+树状数组】

Description

给定一个长度为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的对数。(某位置多次修改为
同样的数值,按多次统计)

Input

第1行两个整数n,q。分别表示数列长度和操作数。
第2行n个正整数,代表初始数列。
第3--q+2行每行一个操作。

Output

对于每次询问操作,输出一个非负整数表示答案

Sample Input

3 5
2 4 3
Query 2 2
Modify 1 3
Query 2 2
Modify 1 2
Query 1 1

Sample Output

2
3
3

HINT

N<=60000 修改操作数<=40000 询问<=50000 Max{a[i]}含修改<=100000

题解:

         首先可以发现所谓的可持久化看成加点即可.

         把(x,a[x])看成一个点.然后我们把曼哈顿距离转化成切比雪夫距离.

         问题就变成了每次询问一个矩形中有多少点.

         把一个矩形拆成4个然后cdq分治+树状数组即可.

         注意坐标不一定都在第一象限,拆分矩形的时候判断一下即可.

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define N 100010
#define M 200010
using namespace std;
int n,m,a[N],tot,x,y,k,cnt,mx,t[M<<2],ans[M];
char ch[10]; 
struct use{int x,y,id,k,v,p;}q[M<<2],p[M<<2];
int read(){
  int x(0);char ch=getchar();
  while(ch<'0'||ch>'9') ch=getchar();
  while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
  return x;             
}
void add(int kind,int p,int x,int y){
  int xx=x-y,yy=x+y;mx=max(yy,mx);//cout<<xx<<' '<<yy<<endl;
  if (kind==0){q[++tot].x=xx;q[tot].y=yy;q[tot].id=tot;q[tot].k=kind;}
  else{
    int x1=xx-k,y1=yy-k,x2=xx+k,y2=yy+k;mx=max(mx,y2);
    if (y1>0){q[++tot].id=tot;q[tot].k=kind;q[tot].x=x1-1;q[tot].y=y1-1;q[tot].v=1;q[tot].p=p;}
    q[++tot].id=tot;q[tot].k=kind;q[tot].x=x1-1;q[tot].y=y2;q[tot].v=-1;q[tot].p=p;
    if (y1>0){q[++tot].id=tot;q[tot].k=kind;q[tot].x=x2;q[tot].y=y1-1;q[tot].v=-1;q[tot].p=p;}
    q[++tot].id=tot;q[tot].k=kind;q[tot].x=x2;q[tot].y=y2;q[tot].v=1;q[tot].p=p;
  }
}
bool cmp(use a,use b){
  if (a.x==b.x){
    if (a.y==b.y) return a.id<b.id;
    else return a.y<b.y;
  }
  return a.x<b.x;
}
int lowbit(int x){return x&(-x);}
void add(int x,int v){for (int i=x;i<=mx;i+=lowbit(i)) t[i]+=v;}
int query(int x){
  int ans(0);
  for (int i=x;i;i-=lowbit(i)) ans+=t[i];
  return ans;
}
void solve(int l,int r){
  if (l==r) return;
  int mid=(l+r)>>1,l1=l,l2=mid+1;
  for (int i=l;i<=r;i++){
    if (q[i].id<=mid&&!q[i].k) add(q[i].y,1);
    if (q[i].id>mid&&q[i].k) ans[q[i].p]+=query(q[i].y)*q[i].v;
  }  
  for (int i=l;i<=r;i++)
    if (q[i].id<=mid&&!q[i].k) add(q[i].y,-1); 
  for (int i=l;i<=r;i++)
    if (q[i].id<=mid) p[l1++]=q[i];
    else p[l2++]=q[i];
  for (int i=l;i<=r;i++) q[i]=p[i];
  solve(l,mid);solve(mid+1,r);
}
int main(){
  n=read();m=read();
  for (int i=1;i<=n;i++)
    a[i]=read(),add(0,0,i,a[i]);
  for (int i=n+1;i<=n+m;i++){
    scanf("%s",ch);
    if (ch[0]=='M') x=read(),y=read(),add(0,0,x,y),a[x]=y;
    else x=read(),k=read(),add(1,++cnt,x,a[x]);
  }
  sort(q+1,q+tot+1,cmp);mx++;
  solve(1,tot);
  for (int i=1;i<=cnt;i++) printf("%d\n",ans[i]);  
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值