CF1093G 高维曼哈顿距离/线段树

原题链接:Multidimensional Queries

题目大意

\(n\)个在\(k\)维空间里的点,第\(i\)个点保存在\(a_i\)这个长度为\(k\)的向量中,定义两个点之间的曼哈顿距离是\(\sum\limits_{i=1}^k|a_{x,i}-a_{y,i}|\).给定\(q\)个询问,每个询问形如下面两种:

  • \(1\space i\space b_1\space b_2\space b_3...b_k\)表示把第\(i\)个点替换成\({b_1,b_2,b_3,...b_k}\).
  • \(2\space l \space r\)表示在两个点\(a_i\)\(a_j\)之间的最大距离,其中\(l \leq i,j\leq r\).

数据范围:

\(1 \leq n \leq 2 * 10^5\)

\(1 \leq k \leq 5\)

\(10^{-6} \leq a_{i,j} \leq 10^6\)

\(1 \leq q \leq 2 * 10^5\)

数据保证修改的点坐标范围也不会越界,且至少有一个询问操作.

思路

以下在讨论的时候,其中一个点的下标记作是\({x1,x2,x3...xk}\)另外一个点是\({y1,y2,y3...yk}\).

不妨先考虑这个\(k\)维版本的子问题:如果只有\(2\)维应该怎么做,关于这个我之前有篇博客也是一个套路的做法(在\(2\)维下有更好的形式),这里可以搬过来:

\(|x_1 - x_2| + |y_1 - y_2|\)

=\(\max(x_1 - x_2,x_2 - x_1) + \max(y_1 - y_2,y_2 - y_1)\)

=\(\max((x_1 - x_2)+(y_1 - y_2),(x_1 - x_2)+(y_2 - y_1),(x_2 - x_1) +(y_1 - y_2),(x_2 - x_1)+(y_2 - y_1))\)

这里记\(s[x][0] = -x1 - x2,s[x][1] = -x1 +x2,s[x][2] = x1 - x2,s[x][3] = x1 +x2\)对于\(y\)也是对称的定义.

那么表达式可以换成:

=\(\max\limits_{j\in[0,3]}(s[x][j] - s[y][j])\)

注意到\(s[x][j]\)的下标\(j\)\(x_j\)的正负取值的关系,对应就是当\(j\)\(1\)的时候是正,取\(0\)的时候是负的.

也就是说可以写成\(s[x][j] = \sum\limits_{i=1}^kx_i *((j >> i \& 1) ? 1 : -1)\).

考虑如果有\(n\)个点,那么最大值应该是\(\max\limits_{k\in[0,3]}(\max\{a_{x,k}\} - \min\{a_{y,k}\}\)).这个式子相当于是在枚举\(k\),并且找到对于当前的\(k\)来说哪个点\(a_i\)的值最大,哪个点对应的值最小,最大减最小就是最大值.那么进而可以把这个做法推广到更高维:由于\(k\leq 5\)所以一共有\(2^5\)位,每一位取值枚举就可以了,对于查询操作,等于说是在限定的某些点之中找出他们的距离最大值,\(k\)是可以枚举的一共只有\(32\)位,剩下的就是找\(s[x][k]\)的最大值和最小值,这部分由于\(x\)的大小有区间限制,套一个RMQ就可以解决了.

这里使用线段树维护,一个非常粗暴的做法是直接对每个\(k\)开一个线段树,这样做常数太大了,可以把\(32\)个值全部压入一个节点里,那么线段树上一个节点储存的就是\([l,r]\)这个区间里最大/小的\(s[x][k]\)的值,记作\(maxv[k]\)和最小值\(minv[k]\).直接维护就可以了.对于修改操作直接暴力重置最后一个叶子节点再不断pushup上去就可以了.整个操作携带\(32\)的常数,不影响整体复杂度.查询操作直接枚举\(k\)就可以了.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	

const int N = 2e5+7,M = 32,INF = 0x3f3f3f3f;
struct Node
{
	int l,r;
	int maxv[M],minv[M];
}tr[N * 4];
int a[N][M];

void pushup(int u)
{
	forn(k,0,31)
	{
		tr[u].maxv[k] = max(tr[u << 1].maxv[k],tr[u << 1 | 1].maxv[k]);
		tr[u].minv[k] = min(tr[u << 1].minv[k],tr[u << 1 | 1].minv[k]);
	}	
}

void build(int u,int l,int r)
{
	if(l == r)
	{
		tr[u] = {l,r};
		forn(j,0,31)
		{
			int s_x_j = 0;
			forn(i,0,31)	s_x_j += a[l][i] * ((j >> i & 1) ? 1 : -1);
			tr[u].minv[j] = s_x_j;
			tr[u].maxv[j] = s_x_j;
		}
		return ;
	}
	int mid = l + r >> 1;
	tr[u] = {l,r};
	forn(i,0,31)	tr[u].minv[i] = INF,tr[u].maxv[i] = -INF;
	build(u << 1,l,mid),build(u << 1 | 1,mid + 1,r);
	pushup(u);
}

void modify(int u,int x,vector<int>& a)
{
	if(tr[u].l == x && tr[u].r == x)
	{
		forn(j,0,31)
		{
			int s_x_j = 0;
			forn(i,0,31)	s_x_j += a[i] * ((j >> i & 1) ? 1 : -1);
			tr[u].minv[j] = s_x_j;
			tr[u].maxv[j] = s_x_j;
		}
		return ;
	}
	int mid = tr[u].l + tr[u].r >> 1;
	if(x <= mid)	modify(u << 1,x,a);
	if(x > mid)		modify(u << 1 | 1,x,a);
	pushup(u);
}

int query_max(int u,int l,int r,int k)
{
	if(tr[u].l >= l && tr[u].r <= r)	return tr[u].maxv[k];
	int mid = tr[u].l + tr[u].r >> 1,res = -INF;
	if(l <= mid)	res = max(res,query_max(u << 1,l,r,k));
	if(r > mid)		res = max(res,query_max(u << 1 | 1,l,r,k));
	return res;
}

int query_min(int u,int l,int r,int k)
{
	if(tr[u].l >= l && tr[u].r <= r)	return tr[u].minv[k];
	int mid = tr[u].l + tr[u].r >> 1,res = INF;
	if(l <= mid)	res = min(res,query_min(u << 1,l,r,k));
	if(r > mid)		res = min(res,query_min(u << 1 | 1,l,r,k));
	return res;
}

int main()
{
	int n,k;scanf("%d%d",&n,&k);
	forn(i,1,n)	forn(j,0,k - 1)	scanf("%d",&a[i][j]);
	build(1,1,n);
	int q;scanf("%d",&q);
	while(q--)
	{
		int op;scanf("%d",&op);
		if(op == 1)
		{
			int x;scanf("%d",&x);
			vector<int> a(32,0);
			forn(i,0,k - 1)	scanf("%d",&a[i]);
			modify(1,x,a);
		}
		else
		{
			int l,r;scanf("%d%d",&l,&r);
			int res = -INF;
			forn(k,0,31)	res = max(res,query_max(1,l,r,k) - query_min(1,l,r,k));
			printf("%d\n",res);
		}
	}
    return 0;
}
posted @ 2021-01-23 21:46  随处可见的阿宅  阅读(155)  评论(0编辑  收藏  举报