BZOJ2989 数列

原题链接:https://www.lydsy.com/JudgeOnline/problem.php?id=2989

数列

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

题解

首先,因为我们要考虑所有的历史版本,所以修改的实质其实是添加一个数到数列里。

其次,我们可以将数列表示为平面上的一堆点:将第 i i 个数ai表示为 (i,ai) ( i , a i )

那么,考虑题目的要求:找有几个点满足 |x1x2|+|y1y2|k | x 1 − x 2 | + | y 1 − y 2 | ≤ k ,放到平面上,我们可以发现,对于一个点 (x1,y1) ( x 1 , y 1 ) 满足上述条件的点的集合,是一个正方形,以 (6,5) ( 6 , 5 ) k=3 k = 3 为例:

那么我们要询问的实际上就是,正方形中的点数。

为了便于处理,我们显然要把正方形旋转 45 45 ∘ ,摆成正的,这样每个询问就可以拆成 4 4 个,然后就只需要乖乖cdq即可 AC A C

代码

注意判断不在第一象限的情况。

#include<bits/stdc++.h>
#define up(x) MX=max(MX,x)
using namespace std;
const int M=1e6;
struct sd{int op,x,y,val,id,t;};
bool operator <(sd a,sd b)
{
    if(a.x!=b.x)return a.x<b.x;
    if(a.y!=b.y)return a.y<b.y;
    return a.op<b.op;
}
sd ope[M],tmp[M];
int n,q,que[M],ans[M],sum[M],MX,tot,base=1;
bool vis[M];
void off(int op,int id,int x,int y,int k)
{
    int xx=x-y,yy=x+y,x1,x2,y1,y2;
    if(!op){up(xx),up(yy),ope[++tot]=(sd){op,xx,yy,0,id,tot};}
    else
    {
        x1=xx-k,y1=yy-k,x2=xx+k,y2=yy+k;
        if(y1>0)ope[++tot]=(sd){op,x1-1,y1-1,1,id,tot};
        ope[++tot]=(sd){op,x1-1,y2,-1,id,tot};
        if(y1>0)ope[++tot]=(sd){op,x2,y1-1,-1,id,tot};
        ope[++tot]=(sd){op,x2,y2,1,id,tot};
        up(x2),up(y2);
    }
}
void in()
{
    char ch[10];
    int a,b;
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;++i)scanf("%d",&que[i]),off(0,i,i,que[i],0);
    for(int i=n+1;i<=n+q;++i)
    {
        scanf("%s%d%d",ch,&a,&b);
        if(ch[0]=='M'){que[a]=b;off(0,i,a,b,0);}
        else off(1,i,a,que[a],b),vis[i]=1;
    }
}
void add(int v,int s){v+=base;for(;v;v>>=1)sum[v]+=s;}
int query(int ri)
{
    int ans=0,le=base;ri+=base+1;
    for(;le^ri^1;le>>=1,ri>>=1)
    {
        if(le&1^1)ans+=sum[le+1];
        if(ri&1)ans+=sum[ri-1];
    }
    return ans;
}
void cdq(int le,int ri)
{
    if(le==ri)return;
    int mid=le+ri>>1,p1=le,p2=mid+1;
    for(int i=le;i<=ri;++i)
    {
        if(!ope[i].op&&ope[i].t<=mid)add(ope[i].y,1);
        if(ope[i].op&&ope[i].t>mid)ans[ope[i].id]+=ope[i].val*query(ope[i].y);
    }
    for(int i=le;i<=ri;++i)if(!ope[i].op&&ope[i].t<=mid)add(ope[i].y,-1);
    for(int i=le;i<=ri;++i)
    if(ope[i].t<=mid)tmp[p1++]=ope[i];
    else tmp[p2++]=ope[i];
    for(int i=le;i<=ri;++i)ope[i]=tmp[i];
    cdq(le,mid);cdq(mid+1,ri);
}
void ac()
{
    MX++;while(base<MX)base<<=1;
    sort(ope+1,ope+1+tot);cdq(1,tot);
    for(int i=1;i<=q+n;++i)if(vis[i])printf("%d\n",ans[i]);
}
int main()
{
    in();ac();
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ShadyPi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值