「THUPC 2022 初赛」搬砖 nsqrtn 做法


抽象一下问题,设一个变量 \(x\),初始为 \(0\),每次令 \(x\gets x+d\),并同时将 \(d\) 减去砖数在 \((x,x+d]\) 内的砖的代价,如果没有砖就停止,询问一共可以跳几步。

定义:"零砖" 为代价为 \(0\) 的砖,"非零砖" 为代价 \(>0\) 的砖。

\(d>\sqrt n\),则最多跳 \(\sqrt n\) 步,所以可以这一部分可以暴力跳,每次要查询一个区间和,用 \(\mathcal{O}(\sqrt n)\) 单点修改,\(\mathcal{O}(1)\) 区间查询的分块维护即可,这一部分总复杂度就被平衡到了 \(\mathcal{O}(n\sqrt n)\)

\(d\leq \sqrt n\),则 \(d\) 最多减少 \(\sqrt n\) 次,若每次能 \(\mathcal{O}(x)\) 定位 \(d\) 在哪里减少了(或者跳了一个空的区间导致结束),然后在那里暴力跳,这一部分复杂度就是 \(\mathcal{O}(x\sqrt n)\). 

假设我们现在以 \(d\) 为块长,与 \((x+1)\)\(d\) 意义下同余的位置作为块起始端点,进行分块。

定位到 \(d\) 在哪里减少了,相当于查询当前位置后面第一个非零砖的位置,然后算出他所在的块即可。这一部分相当于 01 序列,单点置 1,查询一个位置后面第一个 1,由于修改有 \(\mathcal{O}(n)\) 次,查询有 \(\mathcal{O}(n\sqrt n)\) 次,可以用分块来平衡复杂度做到 \(\mathcal{O}(\sqrt n)\) 修改 \(\mathcal{O}(1)\) 查询。

看是否会跳一个空区间导致结束,相当于看当前这种分块方案下后面第一个不合法的块(合法定义为块里面有砖)是哪一个。注意到是按照块的端点模 \(d\) 的余数分类的,所以所有分块方案的总块数是 \(\sum d\times \frac{n}{d}=\mathcal{O}(n\sqrt n)\)

那么在每一次加入砖的时候,设 \(t\) 为新变成合法块的块数,若能在 \(\mathcal{O}(t)\) 的复杂度内找到遍历所有的新合法块,那么要解决的问题就是 01 序列,单点置 1,查询一个位置后面第一个 0(也就是一个块新变成合法,或者查询一个位置后面第一个不合法块),这个可以并查集维护。

如何 \(\mathcal{O}(t)\) 找到所有新合法块?在每次新加入一个砖的时候,找到它前面第一个砖,后面第一个砖,然后枚举块长 \(d\),就很容易枚举出来了。

综上所述,总复杂度为 \(\mathcal{O}(n\sqrt n\alpha(n))\),采用即可做到时间复杂度 \(\mathcal{O}(n\sqrt n)\)

代码能力太弱了,我的常数有点大,好像跑得不是很快...

//O(n sqrtn a(n))
#include
#include
#include
#include
#include
#include
#define pb emplace_back
#define mp std::make_pair
#define fi first
#define se second
#define int long long
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pairpii;
typedef pairpli;
typedef pairpll;
typedef vectorvi;
typedef vectorvll;
typedef vectorvpii;
templateT cmax(T &x, T y){return x=x>y?x:y;}
templateT cmin(T &x, T y){return x=x
T &read(T &r){
	r=0;bool w=0;char ch=getchar();
	while(ch<'0'||ch>'9')w=ch=='-'?1:0,ch=getchar();
	while(ch>='0'&&ch<='9')r=r*10+(ch^48),ch=getchar();
	return r=w?-r:r;
}
template
void read(T1 &x, T2& ...y){ read(x); read(y...); }
const int N=100010;
const int K=100000;
const int B=500;
const int M=510;
inline int lowbit(int x){return x&(-x);}
struct Block{
	ll sum[N],suf[N];
	void modify(int x,int v){
		int b=(x-1)/B;
		for(int i=b;i<=(K-1)/B;i++)sum[i]+=v;
		for(int i=x;i>b*B;i--)suf[i]+=v;
	}
	ll sumq(int x){
		return x<=0 ? 0 : sum[(x-1)/B]-(x%B==0?0:suf[x+1]);
	}
	ll query(int x,int y){
		cmin(y,K);
		if(x>y)return 0;
		return sumq(y)-sumq(x-1);
	}
}tr1,tr2;
int one[N],zero[N];
int tag[N],va[N];
void ins(int x){
	int b=(x-1)/B;
	for(int i=b-1;~i;--i)cmin(tag[i],x);
	for(int i=x;i>b*B;i--)cmin(va[i],x);
}
int que(int x){
	return min(va[x],tag[(x-1)/B]);
}
setzs,rzs;
vi f[M][M];
int find(int p,int q,int x){
	return f[p][q][x]!=x?f[p][q][x]=find(p,q,f[p][q][x]):x;
}
void merge(int p,int q,int x,int y){
	int fx=find(p,q,x),fy=find(p,q,y);
	f[p][q][fx]=fy;
}
int calc(int x,int y){
	int len;
	if(!y)len=K-(x-1);
	else len=K-(y-1);
	return (len+x-1)/x;
}
int calc2(int pos,int x,int y){
	if(!y)pos-=x-1;
	else pos-=y-1;
	return (pos-1)/x+1;
}
int solve(int d){
	int x=0;
	int ans=1;
	while(d>B&&x<=K){
		if(!tr2.query(x+1,x+d))return ans;
		ll tmp=tr1.query(x+1,x+d);
		x+=d;
		++ans;
		if(d<=tmp)return ans-1;
		d-=tmp;
	}
	if(x>K)return ans;
	while(x<=K){
		int pos1=que(x+1);
		int xd=(x+1)%d;
		int now=calc2(x+1,d,xd);
		int nex=find(d,xd,now);
		nex=min(nex,calc2(pos1,d,xd));
		ans+=nex-now;
		x+=(nex-now)*d;
		//
		if(!tr2.query(x+1,x+d))return ans;
		ll tmp=tr1.query(x+1,x+d);
		x+=d;
		++ans;
		if(d<=tmp)return ans-1;
		d-=tmp;
	}
	return ans;
}
signed main(){
	for(int i=0;i<=K+1;i++)tag[i]=va[i]=K+1;
	zs.insert(K+1);
	rzs.insert(K+1);
	for(int i=1;i<=B;i++){
		for(int j=0;j