【校内模拟】火神的鱼(分块)(均摊分析)

传送门是不可能有的了


题解:

联赛组今天考试的T3,觉得很有科学打脸价值就来写一写。

首先正解是一个需要四棵线段树的 O ( n log ⁡ n + m log ⁡ n ) O(n\log n+m\log n) O(nlogn+mlogn)做法,常数巨大。

本着科学打脸观。我写了一个 O ( ( n + m ) n ) O((n+m)\sqrt n) O((n+m)n )跑过了大多数人的线段树。

其实卡常就是按照常规卡了一下,莫名其妙就跑进了 3 s 3s 3s以内。

由于加的东西是单调的,所以我们用最后的渔网的横纵坐标将平面切割成九块。

但是实际上只有左下角四块的点需要考虑,设立四个边界。

每块维护一下下一次出现需要跨边界的点需要加多少 x x x y y y

每个点最多跨越三次边界,在块中出现点跨越边界的时候重构整块,每个点对所在块的重构次数贡献是 O ( 1 ) O(1) O(1)的,每次重构复杂度是 O ( n ) O(\sqrt n) O(n )的,单个块重构的均摊复杂度为 O ( n ) ∗ O ( n ) = O ( n ) O(\sqrt n)*O(\sqrt n)=O(n) O(n )O(n )=O(n)。一共有 O ( n ) O(\sqrt n) O(n )块,总复杂度为 O ( n n ) O(n\sqrt n) O(nn )

散块的修改也可以需要直接重构。每次最多重构两块,复杂度为 O ( m n ) O(m\sqrt n) O(mn )

由于散块的重构是卡满了的,缩小块长就可以轻易卡下常数。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define gc get_char
#define cs const

namespace IO{
	inline char get_char(){
		static cs int Rlen=1<<22|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	
	template<typename T>
	inline T get(){
		char c;bool f=0;
		while(!isdigit(c=gc()))f=c=='-';T num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return f?-num:num;
	}
	inline int getint(){return get<int>();} 
}
using namespace IO;

using std::cerr;
using std::cout;

cs int N=3e4+5;
#define x1 x_1
#define x2 x_2
#define y1 y_1
#define y2 y_2

int n,m;
int x1,x2,y1,y2;
int B,bcnt;
int bl[N],pl[N],pr[N],ans[N];
int x[N],y[N],addx[N],addy[N],nxtx[N],nxty[N];

inline void rebuild(int id){
	ans[id]=0;nxtx[id]=0x3f3f3f3f,nxty[id]=0x3f3f3f3f;
	for(int re i=pl[id];i<=pr[id];++i){
		x[i]+=addx[id],y[i]+=addy[id];
		if(x[i]>x2||y[i]>y2)continue;
		(x1<=x[i]&&x[i]<=x2&&y1<=y[i]&&y[i]<=y2)&&(++ans[id]);
		nxtx[id]=std::min(nxtx[id],x[i]<x1?x1-x[i]:x2+1-x[i]);
		nxty[id]=std::min(nxty[id],y[i]<y1?y1-y[i]:y2+1-y[i]);
	}
	addx[id]=addy[id]=0;
}

inline void modify_x(int l,int r,int d){
	if(bl[l]==bl[r]){
		for(int re i=l;i<=r;++i)x[i]+=d;
		rebuild(bl[l]);
		return ;
	}
	if(l==pl[bl[l]])--l;
	else {
		for(int re i=l;i<=pr[bl[l]];++i)x[i]+=d;
		rebuild(bl[l]);
	}
	if(r==pr[bl[r]])++r;
	else {
		for(int re i=r;i>=pl[bl[r]];--i)x[i]+=d;
		rebuild(bl[r]);
	}
	for(int re i=bl[l]+1;i<bl[r];++i)if((addx[i]+=d)>=nxtx[i])rebuild(i);
}

inline void modify_y(int l,int r,int d){
	if(bl[l]==bl[r]){
		for(int re i=l;i<=r;++i)y[i]+=d;
		rebuild(bl[l]);
		return ;
	}
	if(l==pl[bl[l]])--l;
	else {
		for(int re i=l;i<=pr[bl[l]];++i)y[i]+=d;
		rebuild(bl[l]);
	}
	if(r==pr[bl[r]])++r;
	else {
		for(int re i=r;i>=pl[bl[r]];--i)y[i]+=d;
		rebuild(bl[r]);
	}
	for(int re i=bl[l]+1;i<bl[r];++i)if((addy[i]+=d)>=nxty[i])rebuild(i);
}

inline int query(int l,int r){int res=0;
	if(bl[l]==bl[r]){
		if(l==pl[bl[l]]&&r==pr[bl[r]])res=ans[bl[l]];
		else {
			for(int re i=l,dx=addx[bl[l]],dy=addy[bl[l]];i<=r;++i)
			res+=(x1<=x[i]+dx&&x[i]+dx<=x2&&y1<=y[i]+dy&&y[i]+dy<=y2);
		}
		return res;
	}
	if(l==pl[bl[l]])res+=ans[bl[l]];
	else{
		for(int re i=l,dx=addx[bl[l]],dy=addy[bl[l]];i<=pr[bl[l]];++i)
		res+=(x1<=x[i]+dx&&x[i]+dx<=x2&&y1<=y[i]+dy&&y[i]+dy<=y2);
	}
	if(r==pr[bl[r]])res+=ans[bl[r]];
	else {
		for(int re i=r,dx=addx[bl[r]],dy=addy[bl[r]];i>=pl[bl[r]];--i)
		res+=(x1<=x[i]+dx&&x[i]+dx<=x2&&y1<=y[i]+dy&&y[i]+dy<=y2);
	}
	for(int re i=bl[l]+1;i<bl[r];++i)res+=ans[i];
	return res;
}

inline void solve(){
	n=getint(),x1=getint(),y1=getint(),x2=getint(),y2=getint();
	B=std::max(1.0,sqrt(n)*0.3);bcnt=0;
	for(int re i=1;i<=n;++i){
		x[i]=getint(),y[i]=getint();
		if((i-1)%B==0){
			pl[++bcnt]=i;pr[bcnt-1]=i-1;
			addx[bcnt]=addy[bcnt]=ans[bcnt]=0;
		}
		bl[i]=bcnt;
	}bl[n+1]=bcnt+1;
	for(int re i=1;i<=bcnt;++i)rebuild(i);
	m=getint();int l,r,d;
	while(m--)switch(getint()){
		case 1:l=getint(),r=getint(),d=getint();modify_x(l,r,d);break;
		case 2:l=getint(),r=getint(),d=getint();modify_y(l,r,d);break;
		case 3:l=getint(),r=getint();cout<<query(l,r)<<"\n";break;
	}
}

signed main(){
//	freopen("fish.in","r",stdin);//freopen("fish.out","w",stdout);
	int T=getint();
	while(T--)solve(); 
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值