jzoj3615 【NOI2014模拟】数列 (曼哈顿距离转切比雪夫距离,坐标旋转)

86 篇文章 0 订阅
28 篇文章 0 订阅

题面

给定一个长度为n的正整数数列a[i]。
定义2个位置的f值为两者位置差与数值差的和,即f(x,y)=|x-y|+|a[x]-a[y]|。
你需要写一个程序支持2种操作(k都是正整数):
Modify x k:将第x个数的值修改为k。
Query x k:询问有几个i满足f(x,i)<=k。询问不仅要考虑当前数列,还要考虑任意历史版本,即统计任意位置上出现过的任意数值与当前的a[x]的f值<=k的对数。(某位置多次修改为同样的数值,按多次统计)

n<=6e4,操作数<=5e4,a[i]<=8e4,时限2s

解答

建点(x,a[x]),原题转换为求曼哈顿距离小于等于k的点。
经典模型,将其坐标轴顺时针旋转45*,为了不涉及浮点运算整体放大 2 2 倍。
坐标变化: (x,y)->(x-y,x+y),画图推知,关注原(x,0),原(0,y)即可发现规律。
转化为经典的矩阵求点数。
解法1:cdq分治+扫描线+差分,nlog2 (比较麻烦,离线)
解法2:cdq分治+ 2b 主席树,nlog2(更麻烦,离线)
解法3:二维数据结构,可修主席树nlog2(在线,短,推荐

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#define abs(x) ((x)>0?(x):-(x))
#define _(x,y) (x)-(y),(x)+(y)
#define lowbit(x) ((x)&-(x))
using namespace std;
const int N=6e4+10,DS=32000100,L=-1.2e5+10,R=1.2e5+10,ssd=1.2e5;
int n,q,a[N],root[2*R],tot,sum[DS],t[DS][2];

char c[50];
void change(int &x,int l,int r,int v) {
    if (l>v || r<v) return;
    if (!x)x=++tot;
    if (l==r) {sum[x]++; return;}
    if (v<=(l+r>>1)) change(t[x][0],l,l+r>>1,v);
    else change(t[x][1],(l+r>>1)+1,r,v);
    sum[x]=sum[t[x][0]]+sum[t[x][1]];
}
void _qu(int &x,int l,int r,int tl,int tr,int &v) {
    if (tr<l || tl>r || !x) return;
    if (tl<=l && r<=tr) v+=sum[x]; else {
        _qu(t[x][0],l,l+r>>1,tl,tr,v);
        _qu(t[x][1],(l+r>>1)+1,r,tl,tr,v);
    }
}
void add(int x,int y) {
    for (x+=ssd; x<R*2; x+=lowbit(x)) change(root[x],L,R,y);
}
int query(int x,int ly,int uy) {
    int ret=0;
    for (x+=ssd; x; x-=lowbit(x)) _qu(root[x],L,R,ly,uy,ret);
    return ret;
}
int solve(int lx,int ly,int ux,int uy) {
    lx=max(lx,L+1),ly=max(ly,L-1);
    ux=min(ux,R-1),uy=min(uy,R-1);
    return query(ux,ly,uy)-query(lx-1,ly,uy);
}
int main() {
    freopen("3.in","r",stdin); 
    freopen("3.out","w",stdout);
    cin>>n>>q;
    for (int i=1; i<=n; i++) {
        scanf("%d",&a[i]); add(_(i,a[i]));
    }
    for (int i=n+1; i<=n+q; i++) {
        int x,k; scanf("%s %d %d",c,&x,&k);
        if(c[0]=='M') {
            a[x]=k; add(_(x,k));
        } else printf("%d\n",solve(_(x-k,a[x]),_(x+k,a[x])));
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值