- \(\frak Description\)
- \(\frak Solution\)
- \(\frak Code\)
\(\frak Description\)
\(\frak Solution\)
对于每个 \(x\),考虑满足 \(\text{graze}(x,i)\le k\) 的 \(i\) 满足什么条件:容易想到将每个数搞成一个点对 \((i,a_i)\),这样 \(\text{graze}(x,i)\) 其实就是曼哈顿距离,那么合法的点 \(i\) 显然围绕点 \(x\) 形成一个正方形,只不过是旋转了 \(45^\circ\). 问题就转化成统计在这个正方形的点的数量。
我们可以用切比雪夫距离来再转化一下,点就变成了 \((a_i+i,a_i-i)\),但同时我们又将正方形转回来了。如果想看具体推导可以看看 \(\rm details\):
对于曼哈顿距离 \(D\) 有:
所以将点坐标转化成 \((x+y,x-y)\) 或者 \((y+x,y-x)\) 都可以用切比雪夫距离表示原来的 \(D\).
如果没有强制在线,现在就可以用 \(\rm cdq\) 分治 + 扫描线做到 \(\mathcal O(n\log^2 n)\)(以下均设 \(n\) 与值域同阶). 不过如果强制在线呢?这儿有个 并不船新 的 \(\rm trick\):二进制分组!
二进制分组会把修改分为若干大小为 \(2\) 的幂次的组,每组用一个数据结构维护。新加入一个修改时,会根据修改个数和前面的修改进行合并(这个合并类似于二进制的进位,即,两个大小为 \(2\) 的组合并成大小为 \(4\) 的组,是不是很像 \(1024\) )。查询时在每一组的数据结构里分别查询。
这有啥用呢?有了它,我们就可以对于每一组维护一棵主席树,合并的时候暴力重构即可。
询问复杂度是 \(\mathcal O(q\log^2 n)\) 的,现在我们来看看合并复杂度:假设这是第 \(i\) 次修改,那么新加入的点就会和之前约 \(\text{lowbit}(i)\) 个点合并,毕竟如果将 \(i\) 减一,此时 \(\log_2(\text{lowbit}(i))\) 位之后全为 \(1\),依次合并上去就是约 \(\text{lowbit}(i)\) 个点。合并的主要复杂度在于 recycle()
,此时复杂度可以表示为 \(f(2^i)=\sum_{j=0}^{i-1}2^j\cdot \log(2^j)=\sum_{j=0}^{i-1}j\cdot 2^j\).
考虑枚举每个 \(2\) 的幂作为 \(\rm lowbit\),总合并复杂度就是:
欸?后面那一坨不就是高中数列的经典题吗?化一化可以得到:
\(\frak Code\)
#include <cstdio>
#define print(x,y) write(x),putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; char s; bool f=0;
while((s=getchar())>'9' or s<'0')
f |= (s=='-');
while(s>='0' and s<='9')
x = (x<<1)+(x<<3)+(s^48),
s = getchar();
return f?-x:x;
}
template <class T>
inline void write(T x) {
static int writ[40],w_tp=0;
if(x<0) putchar('-'),x=-x;
do writ[++w_tp]=(x-x/10*10),x/=10; while(x);
while(putchar(writ[w_tp--]^48),w_tp);
}
#include <vector>
#include <algorithm>
using namespace std;
typedef pair <int,int> pii;
const int B = 6e4+2;
const int lim = 2e5;
const int maxn = 6e4+5;
const int SIZE = maxn*40;
int n,q,a[maxn],rec[SIZE],tp,idx;
int rt[20][(lim>>1)+5],tail;
struct node {
bool ban; int siz,son[2];
} t[SIZE];
vector <pii> v[20];
void recycle(int o) {
if(t[o].ban || !o) return;
rec[++ tp]=o, t[o].ban=1;
// 因为主席树节点可能有很多爸爸,所以不妨直接将节点状态设置成 "ban" 而不是取消它与爸爸的关系
recycle(t[o].son[0]);
recycle(t[o].son[1]);
}
int NewNode(int p) {
int x = tp?rec[tp--]:++idx;
t[x] = t[p]; t[x].ban = false;
return x;
}
void modify(int &o,int p,int l,int r,int pos) {
o=NewNode(p); ++ t[o].siz;
if(l==r) return;
int mid = l+r>>1;
if(pos<=mid) modify(t[o].son[0],t[p].son[0],l,mid,pos);
else modify(t[o].son[1],t[p].son[1],mid+1,r,pos);
}
void rebuild(int x) {
sort(v[x].begin(),v[x].end());
for(int i=0; i<v[x].size(); ++i)
modify(rt[x][i],i?rt[x][i-1]:0,1,lim,v[x][i].second);
}
void ins(int x,int y) {
vector <pii> t;
t.push_back(make_pair(x,y));
while(tail && v[tail].size()==t.size()) {
for(int i=0; i<v[tail].size(); ++i)
t.push_back(v[tail][i]),
recycle(rt[tail][i]);
-- tail;
}
v[++ tail] = t;
rebuild(tail);
}
int query(int a,int b,int l,int r,int L,int R) {
if(!b) return 0;
if(l>=L && r<=R) return t[b].siz-t[a].siz;
int mid = l+r>>1, ans=0;
if(L<=mid) ans = query(t[a].son[0],t[b].son[0],l,mid,L,R);
if(R>mid) ans += query(t[a].son[1],t[b].son[1],mid+1,r,L,R);
return ans;
}
int ask(int x,int y,int l) {
int ans=0;
for(int i=1;i<=tail;++i) {
int L=lower_bound(v[i].begin(),v[i].end(),make_pair(x-l,0))-v[i].begin()-1;
int R=lower_bound(v[i].begin(),v[i].end(),make_pair(x+l+1,0))-v[i].begin()-1;
ans += query(~L?rt[i][L]:0,~R?rt[i][R]:0,1,lim,y-l,y+l);
}
return ans;
}
int main() {
n=read(9), q=read(9);
for(int i=1;i<=n;++i)
a[i]=read(9), ins(a[i]+i,a[i]-i+B);
char opt[10]; int x,y;
while(q --) {
scanf("%s %d %d",opt+1,&x,&y);
if(opt[1]=='M') {
a[x] = y;
ins(a[x]+x,a[x]-x+B);
} else
print(ask(a[x]+x,a[x]-x+B,y),'\n');
}
return 0;
}