CF::Gym 100739E - Life as a Monster

CF::Gym题目页面传送门

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

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

强制在线。

\(n,q\in\left[0,10^5\right]\)\(\mathrm{TL}=1\mathrm s,\mathrm{ML}=64\mathrm{MB}\)

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

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

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

\[\begin{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'\right),\left(x_2',y_2'\right)\)的曼哈顿距离,不难得到四元一次方程组

\[\begin{cases}x_1'+y_1'=x_1\\x_1'-y_1'=y_1\\x_2'+y_2'=x_2\\x_2'-y_2'=y_2\end{cases} \]

解得

\[\begin{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}2\end{cases} \]

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

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

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

BIT?线段树?不行,因为强制在线不能离散化,基于值域建不了。动态开点线段树?抱歉,会炸空间。只剩一个选择了,就是平衡树(这里使用fhq-Treap)。维护子树内的坐标之和与实际存在的点的个数;计算答案时在平衡树内询问某个半平面内的信息,另一个半平面内的信息就用总坐标和与总点数(\(n\))减去;一次单点增加/减少就插入一个节点,权值是对应要增加/减少的值(的相反数)。时间复杂度\(\mathrm O(q\log 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<<"\n";
		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]<<"\n";
	}
	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<<"\n";
			int tmp=x;
			x+=y;y=tmp-y;//转距离 
//			cout<<x<<" "<<y<<"\n";
			pair<int,int> res1=trp_x.suM(x),res2=trp_y.suM(y);//询问某个半平面内的信息 
//			printf("res1=(%lld,%lld) res2=(%lld,%lld)\n",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\n",lasans);
			lasans%=base;//%%%hsctxdy
		}
	}
	return 0;
}
posted @ 2020-07-22 16:50  ycx060617  阅读(135)  评论(2编辑  收藏  举报