一种基于时间顺序对序列操作进行操作的算法,陈丹琦大佬提出的,也称时间分治。
<<算法竞赛进阶指南>>上解释得就挺不错的
就是先把一系列的操作存起来,二分去操作,每次计算左区间对右区间的影响,递归边界就是L==R,即只有一个操作时没有意义。刚开始觉得挺难的,做了几道题之后觉得还好,这种思想奇妙。
最开始做的cdq大部分应该都是这道题陌上花开。这题我是很久之前做的了,当时不理解cdq分治的精髓,瞎写的。
现在又重新弄起了cdq分治。
为什么能够对左右区间的操作排序呢?
这里我们是把左区间的修改操作以及右区间查询操作提出来,那么就变成了一个静态问题,因为是来自不同的区间,所以提出来的所有操作中修改操作都是在询问操作之前的,所以可以排序,就不存在把一个尚未发生的修改操作提到某一个查询操作的前面。
为什么每次要撤销操作呢?
如果我们每次都是用memset来初始化,nlog(n)次递归,每一次都是
O
(
n
)
O(n)
O(n)的初始化,复杂度将会提高一个级数。所以每次离开前都撤销此次的操作,就可以保证下次再用时还是初始化状态。
莫基亚其实挺简单的,查询的是区间,然后是单点修改。如果不是数据太大,那么用树状数组就可以很方便的解决了。只可惜开不了2e6的二维数组。于是考虑cdq分治维持一下生活了。根据容斥原理对于每一个查询(x1,y1)----(x2,y2),可以分解成四个询问操作,即+(x2,y2), +(x1-1,x2-1), -(x1-1,y2), -(x2,y1-1)。那么cdq分治时,如何维护区间的影响呢。 提取左区间的修改操作,以及右区间的询问操作。当时我是想按x,y,t的关系企图把他们排序成对于每一个询问操作,对它有影响的修改操作在他前面 的这种情况,尝试了多种排序之后无功而返,于是便放弃了。还是改用对x排序,树状数组维护y的方法AC了。
计算区间影响:
对x排序完之后,从头开始扫描,如果是修改操作,则在y位置添加a,如果是查询操作那就查询y之前的(包括y)的和,记得在乘以此次查询的符号(+1或-1),最后离开的时候还原刚才的操作。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 2000 * 1000 + 10;
ll s, w;
ll ans[N];
ll limit=0;
struct op{
ll t, x, y,a;
op() {}
op(int _t, int _x, int _y,int _a) {
t = _t; x = _x; y = _y; a = _a;
}
bool operator < (const op a)const {
if (x != a.x)return x < a.x;
if (y != a.y)y < a.y;
return t < a.t;
}
}o[N],a[N];
struct {
int s[N];
void init() {
for (int i = 1; i <= limit; i++) {
s[i] = 0;
}
}
int lowerbit(int x) { return x & -x; }
void add(int x, ll k) {while (x <= limit) {s[x] += k;x += lowerbit(x);}}
ll ask(int x) {ll ans=0;while (x) {ans += s[x];x -= lowerbit(x);}return ans;}
void Clear(int x) { while (x <= limit)s[x] = 0, x += lowerbit(x); }
}bit;
void cdq(int l, int r) {
if (l >= r)return;
int mid = l + r >> 1;
cdq(l, mid);
cdq(mid + 1, r);
int cnt = 0;
ll sum = 0;
for (int i = l; i <= mid; i++)if (o[i].t == 1)a[++cnt] = op(i,o[i].x,o[i].y,o[i].a);
for (int i = mid + 1; i <= r; i++)if (o[i].t == 2)a[++cnt] = op(i,o[i].x,o[i].y,o[i].a);
sort(a+1,a+1+cnt);
for (int i = 1; i<=cnt ; i++) {
if (o[a[i].t].t == 1) {
bit.add(a[i].y,a[i].a);
}
else {
ans[a[i].t] += bit.ask(a[i].y)*a[i].a;
}
}
for (int i = 1; i <= cnt; i++)if (o[a[i].t].t == 1)bit.Clear(a[i].y);
}
int main() {
scanf("%lld%lld",&s,&w);
int cmd;
int cnt = 0;
while (scanf("%d", &cmd), cmd != 3) {
int x1, y1, x2, y2, a;
if (cmd == 1) {
scanf("%d%d%d",&x1,&y1,&a);
o[++cnt] = op(1,x1,y1,a);
}
else {
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
o[++cnt] = op(2,x2,y2,1);
o[++cnt] = op(2, x1 - 1, y1 - 1, 1);
o[++cnt] = op(2, x1 - 1, y2, -1);
o[++cnt] = op(2, x2, y1 - 1, -1);
}
}
for (int i = 1; i <= cnt; i++)limit = max(limit,o[i].y);
bit.init();
cdq(1, cnt);
for (int i = 1; i <= cnt; i++) {
if (o[i].t == 2)ans[i] += (o[i].x*o[i].y*o[i].a*s);
}
for (int i = 1; i <= cnt; i++) {
if (o[i].t == 2)printf("%lld\n",ans[i]+ans[i+1]+ans[i+2]+ans[i+3]),i+=3;
}
return 0;
}
天使玩偶 我是先做的这道题然后才去做上面那题的,所以觉得上面那题是真的挺简单的。上面的那题只有一个方向。而这题有四个方向。
**题意:**初始时给你n个点的坐标,之后m的操作,操作1,新增一个点,操作2,询问已经存在的点距离给出的点的最进距离,这个距离定义为曼哈顿距离。
思路:
我们先考虑一个静态问题,就是没有新曾的点,也没有新增的询问,就是解决给定的点中距离特定点的最小距离。假设询问的点是
(
x
,
y
)
(x,y)
(x,y),那么答案就是
m
i
n
(
∣
x
−
x
i
∣
+
∣
y
−
y
i
∣
)
min(|x-x_i|+|y-y_i|)
min(∣x−xi∣+∣y−yi∣)。这个式子含有绝对值符号不好处理,为了去掉绝对值符号,我们把原来的询问分成4个,分别
(
x
,
y
)
(x,y)
(x,y)的左上,左下,右上,右下四个方向,四个结果取最小值即可。
左下方向:
m i n ( ( x − x i ) + ( y − y i ) ) = x + y − m a x ( x i + y i ) min((x-x_i)+(y-y_i))=x+y-max(x_i+y_i) min((x−xi)+(y−yi))=x+y−max(xi+yi)
左上方向:
m i n ( ( x − x i ) + ( y i − y ) ) = ( x − y ) − m a x ( x i − y i ) min((x-x_i)+(y_i-y))=(x-y)-max(x_i-y_i) min((x−xi)+(yi−y))=(x−y)−max(xi−yi)
其它的两个方向推到和上面的类似,记得统一成max的形式,x在前,x在后。
下面的是针对左下方向的
这样之后我们就可以先对x排序,树状数组维护最大值,具体操作如下:
1 如果是一个点,就在树状数组中把第 y i y_i yi个位置与 ( x i + y i ) (x_i+y_i) (xi+yi)取最大值。
2 如果是一个询问,则查询当前树状数组中的最大值val,那么结果就是 x + y − v a l x+y-val x+y−val。
确保完全理解上面的做法后,我们现在加入后面的m次操作,变成了一个动态问题。
我们把题目看成一个n+m个的操作。运用cdq分治。每次提取出左区间新增加的点(初始的n个点也看做事新增的)和右去见询问操作,那么问题就变成了上面的问题。
ok,是不是其实挺简单的。
我自己写的话,肯定是四个方向四个函数,但是看了其他网友大佬的代码,居然巧妙的把它融合到一起了,佩服!!!
如果你推导四个方向式子的时候按我 上门的统一形式,就会发现前后 x , y x,y x,y的正负号是一样得到。那么记录一下,就可以用一个树状数组维护了。如果方向是从上到下的,记得把树状数组的位置反转一下。
AC_CODE
#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
const int N = 1000 * 1000 + 10;
int n, m,limit=0,ans[N];
struct op {
int t, x, y;
op() {}
op(int _t, int _x, int _y) {
t = _t; x = _x; y = _y;
}
bool operator<(const op a)const {
return x < a.x || (x == a.x&&y < a.y);
}
}o[N],a[N];
struct {
int s[N];
void init() {
for (int i = 1; i <= limit; i++)s[i] = -inf;
}
int lowerbit(int x) { return x & -x; }
void add(int x, int k) { while (x <= limit)s[x] = max(s[x], k), x += lowerbit(x); }
int ask(int x) { int ans = -inf; while (x)ans = max(ans, s[x]), x -= lowerbit(x); return ans; }
void Clear(int x) { while (x <= limit)s[x] = -inf, x += lowerbit(x); }
}bit;
void solve(int st, int en, int d, int dx, int dy) {
for (int i = st; i != en; i += d) {
int pos = dy == 1 ? a[i].y : limit - a[i].y + 1;
int k = a[i].x*dx + a[i].y*dy;
if (o[a[i].t].t == 1)bit.add(pos,k);
else ans[a[i].t] = min(ans[a[i].t],k-bit.ask(pos));
}
for (int i = st; i != en; i += d) {
int pos = dy == 1 ? a[i].y : limit - a[i].y + 1;
if (o[a[i].t].t == 1)bit.Clear(pos);
}
}
void cdq(int l, int r) {
if (l >= r)return;
int mid = l + r >> 1;
cdq(l,mid);
cdq(mid + 1, r);
int cnt = 0;
for (int i = l; i <= mid; i++)if (o[i].t == 1)a[++cnt] = op(i, o[i].x, o[i].y);
for (int i = mid + 1; i <= r; i++)if (o[i].t == 2)a[++cnt] = op(i, o[i].x, o[i].y);
sort(a+1,a+1+cnt);
solve(cnt, 0, -1, -1, -1);
solve(1, cnt + 1, 1, 1, -1);
solve(1, cnt + 1, 1, 1, 1);
solve(cnt, 0, -1, -1, 1);
}
int main(){
scanf("%d%d",&n,&m);
for (int i = 1, x, y; i <= n; i++)scanf("%d%d", &x, &y), o[i] = op(1,x,++y);
for (int i = 1, t, x, y; i <= m; i++)scanf("%d%d%d", &t, &x, &y), o[i + n] = op(t, x, ++y);
for (int i = 1; i <= n + m; i++)limit = max(limit,o[i].y);
memset(ans,0x3f,sizeof(ans));
bit.init();
cdq(1, n + m);
for (int i = n + 1; i <= m + n; i++)if (o[i].t == 2)printf("%d\n", ans[i]);
return 0;
}