Description
给定一个长度为\(n\)的正整数数列\(a_i\)。
定义两个位置的\(f\)为两者位置差与数值差的和,即\(f_{x,y}=|x-y|+|a_x-a_y|\)。
你需要写一个程序支持两种操作(\(k\)都是正整数):
- Modify x k:将第\(x\)个数的值修改为\(k\)。
- Query x k:询问有几个\(i\)满足\(f_{x,i}\leq k\)。
询问不仅要考虑当前数列,还要考虑任意历史版本,即统计任意位置上出现过的任意数值与当前的\(a_x\)的\(f\leq k\)的对数。(某位置多次修改为同样的数值,按多次统计)
Input
第\(1\)行两个整数\(n,q\)。分别表示数列长度和操作数。
第\(2\)行\(n\)个正整数,代表初始数列。
第\(3\sim q+2\)行每行一个操作。
Output
对于每次询问操作,输出一个非负整数表示答案。
Analysis
原题是查询旋转\(45^\circ\)的正方形内的点数。
我们将其旋转回来,即旋转\(45^\circ\)。
即化为一个三维偏序问题:
- 时间
- \(x+a_x\)
- \(x-a_x\)
经典的做法是CDQ分治。(用二维线段树/树状数组勿喷)(不会的请自行百度)
对于一段已时间为序的区间,将左段的修改点,右段的查询点提出。
用左段的修改点更新右段的查询点的答案。
对于这样的查询,(众所周知)暴力做法是不可取的。
我们考虑树状数组。
利用树状数组维护上下区间\((h_1,h_2)\)之间的点。
稍加思考,便知道这样是不可取的。
因为这样红色区域的点会被出入树状数组多次。
所以我们考虑避免这样的问题。
一个可行的办法是将正方形左右界提取:\(l-1,r\)
按照横坐标排序。
利用容斥原理,求出点数。(蓝色部分\(-\)黄色部分)
至此,问题得解。
P.S.
瞎BB。
在重置树状数组时,有如下办法:
memset(f,0,sizeof(f));
- 一个一个重新清
方法1 显然 更简单。
但是!!!
对于一个\(SIZE=360000\)的数组,\(O\left(SIZE\times log_2N\right)\) 的复杂度显然是不可取的。
所以应取方法2。
Code
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
struct node
{
int x, y, k, ans, pos;
bool bj;
};
const int MaxAi = 360000, AbsValue = 180000;
int n, q;
node d[120005];
int final[60005];
node E[120005], Q[120005];
int Etot, Qtot;
int f[360005];
int Lowbit(int x)
{
return x & (-x);
}
void Change(int cod, int value)
{
while (cod <= MaxAi)
{
f[cod] += value;
cod += Lowbit(cod);
}
}
int Sum(int x)
{
int rs = 0;
while (x > 0)
{
rs += f[x];
x -= Lowbit(x);
}
return rs;
}
int Query(int l, int r)
{
return Sum(r) - Sum(l - 1);
}
bool cmp(node a, node b)
{
return a.x < b.x;
}
bool Get()
{
int c = 0;
bool rtn;
for (c = getchar(); (c == ' ') || (c == '\r') || (c == '\n'); c = getchar());
rtn = c == 'Q';
for (c = getchar(); (c != ' ') && (c != '\r') && (c != '\n'); c = getchar());
return rtn;
}
void Dfs(int l, int r)
{
if (l >= r)
return;
int mid = (l + r) / 2;
Dfs(l, mid), Dfs(mid + 1, r);
Etot = Qtot = 0;
for (int i = l; i <= mid; i++)
if (!d[i].bj)
{
E[++Etot] = d[i];
E[Etot].pos = i;
}
for (int i = mid + 1; i <= r; i++)
if (d[i].bj)
{
Q[++Qtot] = d[i];
Q[Qtot].x -= Q[Qtot].k * 2 + 1;
Q[Qtot].pos = i;
Q[Qtot].bj = true;
Q[++Qtot] = d[i];
Q[Qtot].pos = i;
Q[Qtot].bj = false;
}
sort(E + 1, E + Etot + 1, cmp);
sort(Q + 1, Q + Qtot + 1, cmp);
//memset(f, 0, sizeof(f)); 方法1
for (int i = 1, j = 1; i <= Qtot; i++)
{
while ((j <= Etot) && (E[j].x <= Q[i].x))
Change(E[j].y + AbsValue, 1), j++;
if (Q[i].bj)
d[Q[i].pos].ans -= Query(Q[i].y - Q[i].k * 2 + AbsValue, Q[i].y + AbsValue);
else
d[Q[i].pos].ans += Query(Q[i].y - Q[i].k * 2 + AbsValue, Q[i].y + AbsValue);
}
//方法2
for (int i = 1, j = 1; i <= Qtot; i++)
{
while ((j <= Etot) && (E[j].x <= Q[i].x))
Change(E[j].y + AbsValue, -1), j++;
}
}
int main(int argc, char const *argv[])
{
freopen("init.in", "r", stdin);
scanf("%d%d", &n, &q);
for (int i = 1; i <= n; i++)
{
int tmp;
scanf("%d", &tmp);
d[i].x = i + tmp;
d[i].y = i - tmp;
final[i] = i;
}
for (int i = 1; i <= q; i++)
{
d[n + i].bj = Get();
if (!d[n + i].bj)
{
scanf("%d%d", &d[n + i].x, &d[n + i].y);
final[d[n + i].x] = n + i;
d[n + i].x += d[n + i].y;
d[n + i].y = d[n + i].x - d[n + i].y * 2;
}
else
{
int tmp;
scanf("%d%d", &tmp, &d[n + i].k);
d[n + i].x = d[final[tmp]].x + d[n + i].k;
d[n + i].y = d[final[tmp]].y + d[n + i].k;
}
}
n += q;
Dfs(1, n);
for (int i = 1; i <= n; i++)
if (d[i].bj)
printf("%d\n", d[i].ans);
return 0;
}