CF::Gym 100739E

CF::Gym题目页面传送门

给定(n)个整数坐标((x_i,y_i))。你需要支持(2)(q)次操作:

  1. ( exttt0 a b c):令(x_a=b,y_a=c)
  2. ( exttt1 a b):一个人从((a,b))出发,每秒可以向八连通的格子走,要从对(forall iin[1,n],(x_i,y_i))完成一个来回,每次回到家要休息(2mathrm s)(共(n-1)次),求总时间。

强制在线。

(n,qinleft[0,10^5 ight])(mathrm{TL}=1mathrm s,mathrm{ML}=64mathrm{MB})

不难发现,对于操作( exttt1)答案就是(sumlimits_{i=1}^n2max(|x_i-a|,|y_i-b|)+2(n-1)),要求的就是前面那个(sum),即((a,b))(n)个点的切比雪夫距离之和。

注意到,这里有绝对值,本身就需要零点分段,两个绝对值外面再套个(max),又要分类讨论,这岂不是很烦?这里需要用到一个非常经典的套路:切比雪夫转曼哈顿。

稍微说一下他们的互换吧,毕竟是第一次用。记得上hb新生班的时候,有一个同学不会abs函数,于是就把abs(x)写成max(x,-x),hm夸他很聪明。在这里,我们也可以把曼哈顿距离中的绝对值拆成(max),最终再合成绝对值:

[egin{aligned}&|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)\=&max((x_1+y_1)-(x_2+y_2),(x_1-y_1)-(x_2-y_2),(x_2-y_2)-(x_1-y_1),(x_2+y_2)-(x_1+y_1))\=&max(max((x_1+y_1)-(x_2+y_2),(x_2+y_2)-(x_1+y_1)),max((x_1-y_1)-(x_2-y_2),(x_2-y_2)-(x_1-y_1)))\=&max(|(x_1+y_1)-(x_2+y_2)|,|(x_1-y_1)-(x_2-y_2)|)end{aligned} ]

于是不难发现,((x_1,y_1),(x_2,y_2))的曼哈顿距离等于((x_1+y_1,x_1-y_1),(x_2+y_2,x_2-y_2))的切比雪夫距离。反过来?我们设((x_1,y_1),(x_2,y_2))的切比雪夫距离等于(left(x_1',y_1' ight),left(x_2',y_2' ight))的曼哈顿距离,不难得到四元一次方程组

[egin{cases}x_1'+y_1'=x_1\x_1'-y_1'=y_1\x_2'+y_2'=x_2\x_2'-y_2'=y_2end{cases} ]

解得

[egin{cases}x_1'=dfrac{x_1+y_1}2\y_1'=dfrac{x_1-y_1}2\x_2'=dfrac{x_2+y_2}2\y_2'=dfrac{x_2-y_2}2end{cases} ]

于是,((x_1,y_1),(x_2,y_2))的切比雪夫距离等于(left(dfrac{x_1+y_1}2,dfrac{x_1-y_1}2 ight),left(dfrac{x_2+y_2}2,dfrac{x_2-y_2}2 ight))的曼哈顿距离。

回到本题,把切比雪夫距离的坐标转成曼哈顿距离的坐标(好了,下面讨论的坐标全是曼哈顿距离的坐标了)。现在(max)没有了,只剩下(+)号。不难想到将两个绝对值零点分段,分成以((a,b))为原点的四个象限,然后就可以二维数点。由于强制在线,所以只能(mathrm O!left(qlog^2v ight)),其中(v)是值域大小。虽然时间上说得过去,但空间毫无疑问会爆炸。

不过,二维数点个*啊?由于变成了(+)号,所以左边的绝对值和右边的绝对值可以分别求和,最终加到一起。这样的话,两个零点分段就是独立的,最多只有一重,也就是将整个平面分成两个半平面(而不是四个)。这样就可以一维数点了/cy

BIT?线段树?不行,因为强制在线不能离散化,基于值域建不了。动态开点线段树?抱歉,会炸空间。只剩一个选择了,就是平衡树(这里使用fhq-Treap)。维护子树内的坐标之和与实际存在的点的个数;计算答案时在平衡树内询问某个半平面内的信息,另一个半平面内的信息就用总坐标和与总点数((n))减去;一次单点增加/减少就插入一个节点,权值是对应要增加/减少的值(的相反数)。时间复杂度(mathrm O(qlog n)),空间复杂度(mathrm O(n))

有个坑点,就是每次的lasans输出完之后要尛一下base,不然下次解压的时候直接乘会爆long long。要不是hsc神仙提醒我,我就不能1A了/qq

代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define mp make_pair
#define X first
#define Y second
mt19937 rng(20060617);
const int N=100000;
int n,qu,base;
int x[N+1],y[N+1];
struct fhq_treap{
	int sz,root;
	struct node{unsigned key;int lson,rson,sz,v1/*键值*/,v2/*坐标*/,v3/*实际点数*/,sum2/*字数内坐标和*/,sum3/*字数内实际点数*/;}nd[3*N+1];
	#define key(p) nd[p].key
	#define lson(p) nd[p].lson
	#define rson(p) nd[p].rson
	#define sz(p) nd[p].sz
	#define v1(p) nd[p].v1
	#define v2(p) nd[p].v2
	#define v3(p) nd[p].v3
	#define sum2(p) nd[p].sum2
	#define sum3(p) nd[p].sum3
	void init(){//初始化,一开始为空 
		sz=root=0; 
		nd[0]=node({0,0,0,0,0,0,0,0,0});
	}
	void sprup(int p){
		sz(p)=sz(lson(p))+1+sz(rson(p));
		sum2(p)=sum2(lson(p))+v2(p)+sum2(rson(p));
		sum3(p)=sum3(lson(p))+v3(p)+sum3(rson(p));
	}
	int nwnd(int v1,int v2,int v3){return nd[++sz]=node({rng(),0,0,1,v1,v2,v3,v2,v3}),sz;}
	pair<int,int> split(int x,int p=-1){~p||(p=root);
		if(!x)return mp(0,p);
		pair<int,int> sp;
		if(x<=sz(lson(p)))return sp=split(x,lson(p)),lson(p)=sp.Y,sprup(p),mp(sp.X,p);
		return sp=split(x-1-sz(lson(p)),rson(p)),rson(p)=sp.X,sprup(p),mp(p,sp.Y);
	}
	int mrg(int p,int q){
		if(!p||!q)return p|q;
		if(key(p)<key(q))return rson(p)=mrg(rson(p),q),sprup(p),p;
		return lson(q)=mrg(p,lson(q)),sprup(q),q;
	}
	int lss(int v,int p=-1){~p||(p=root); 
		if(!p)return 0;
		if(v1(p)<v)return sz(lson(p))+1+lss(v,rson(p));
		return lss(v,lson(p));
	}
	void add(int x,int v2,int v3){//单点增加 
		pair<int,int> sp=split(lss(x));
		root=mrg(mrg(sp.X,nwnd(x,v2,v3)),sp.Y);
	}
	pair<int,int> suM(int x){//后缀查询 
//		cout<<sz<<"
";
		pair<int,int> sp=split(lss(x));
		pair<int,int> res=mp(sum2(sp.Y),sum3(sp.Y));
		return root=mrg(sp.X,sp.Y),res;
	}
}trp_x,trp_y;//各维护两边绝对值之和的平衡树 
signed main(){
	cin>>n>>qu>>base;
	int tot_x=0,tot_y=0;
	for(int i=1;i<=n;i++){
		scanf("%lld%lld",x+i,y+i);
		int tmp=x[i];
		x[i]+=y[i];y[i]=tmp-y[i];//转距离 
		tot_x+=x[i];tot_y+=y[i]; 
//		cout<<x[i]<<" "<<y[i]<<"
";
	}
	trp_x.init();trp_y.init();//平衡树初始化 
	for(int i=1;i<=n;i++)trp_x.add(x[i],x[i],1),trp_y.add(y[i],y[i],1);//将初始的n个节点插入 
	int lasans=0;
	while(qu--){
		int tp,a,b,c,d,e;
		scanf("%lld%lld%lld%lld%lld",&tp,&a,&b,&c,&d);
		if(tp==0){
			scanf("%lld",&e);
			trp_x.add(x[a],-x[a],-1);trp_y.add(y[a],-y[a],-1);//单点减少 
			tot_x-=x[a];tot_y-=y[a];
			x[a]=(b*lasans+c)%base;y[a]=(d*lasans+e)%base;
			int tmp=x[a];
			x[a]+=y[a];y[a]=tmp-y[a];//转距离 
			trp_x.add(x[a],x[a],1);trp_y.add(y[a],y[a],1);//单点增加 
			tot_x+=x[a];tot_y+=y[a];
		}
		else{
			int x=(a*lasans+b)%base,y=(c*lasans+d)%base;
//			cout<<x<<" "<<y<<"
";
			int tmp=x;
			x+=y;y=tmp-y;//转距离 
//			cout<<x<<" "<<y<<"
";
			pair<int,int> res1=trp_x.suM(x),res2=trp_y.suM(y);//询问某个半平面内的信息 
//			printf("res1=(%lld,%lld) res2=(%lld,%lld)
",res1.X,res1.Y,res2.X,res2.Y);
			lasans=res1.X-res1.Y*x+(n-res1.Y)*x-(tot_x-res1.X)+res2.X-res2.Y*y+(n-res2.Y)*y-(tot_y-res2.X);
			lasans+=n-1<<1;//计算答案 
			printf("%lld
",lasans);
			lasans%=base;//%%%hsctxdy
		}
	}
	return 0;
}
原文地址:https://www.cnblogs.com/ycx-akioi/p/CF-Gym-100739E.html