分块习题

P3203 [HNOI2010] 弹飞绵羊

虽然是 LCT 板子,但用来做分块入门

如果没有修改操作,可以 O(n) 求出每个点的答案

对于每个块里的点,预处理出它跳出这个块的步数,那么查询时就可以 O(1) 跳过这些块,查询的复杂度 O(n)

修改一个点时,也就是 O(B) 暴力修改即可

B=n,可以达到 O(n) 的时间复杂度

code
#include<bits/stdc++.h>
using namespace std;

const int N=200010,BB=5010;

int n,m,e[N];
int B,t,L[BB],R[BB],pos[N],f[N],lk[N];

void prework()
{
	B=sqrt(n);  t=n/B;
	for(int i=1; i<=t; i++)
		L[i]=(i-1)*B+1,R[i]=i*B;
	if(R[t]<n)
		t++,L[t]=R[t-1]+1,R[t]=n;
	for(int i=1; i<=t; i++)
	{
		for(int j=R[i]; j>=L[i]; j--)
		{
			pos[j]=i;
			if(j+e[j]>R[i])
				f[j]=1,lk[j]=j+e[j];
			else
				f[j]=f[j+e[j]]+1,lk[j]=lk[j+e[j]];
		} 
	}
}

void change(int x,int v)
{
	int p=pos[x];  e[x]=v;
	for(int i=R[p]; i>=L[p]; i--)
	{
		if(i+e[i]>R[p])
			f[i]=1,lk[i]=i+e[i];
		else
			f[i]=f[i+e[i]]+1,lk[i]=lk[i+e[i]];
	}
}

int ask(int x)
{
	int p=pos[x],res=f[x];
	for(int i=p+1,cur=lk[x]; i<=t; i++)
	{
		res+=f[cur];
		cur=lk[cur];
	}
	return res;
}

int main()
{
	scanf("%d",&n);
	for(int i=1; i<=n; i++)
		scanf("%d",&e[i]);
		
	prework();
	
	scanf("%d",&m);
	while(m--)
	{
		int op,s,k;
		scanf("%d%d",&op,&s);
		
		if(op==1)
			printf("%d\n",ask(++s));
		else
		{
			scanf("%d",&k);
			change(++s,k);
		}
	}

	return 0;
}

P4135 作诗

题意:查询区间内出现偶数次的数

此时每个块的贡献不是独立的,单独考虑每个块不好搞

如果拿一个桶维护每个数出现的次数,即可增量维护好数的个数

s[i][j] 表示块 ij 好数的个数,可以 O(nB) 增量维护得到

cnt[x][j] 表示 x 这个数在 1j 这些块中出现的次数,可以 O(n),差分一下就可以知道一个数在连续的块里出现的次数

查询时,先取出 s[p+1][q1],之后对散块单独处理,增量计算贡献

B=n 可得时间复杂度为 O((n+q)n)

code
#include<bits/stdc++.h>
using namespace std;

const int N=100010,BB=410;

int n,c,m,a[N];
int T[N],cnt[N][BB],s[BB][BB]; 
int B,t,L[BB],R[BB],pos[N];

void prework()
{
	B=sqrt(n);  t=n/B;
	for(int i=1; i<=t; i++)
		L[i]=(i-1)*B+1,R[i]=i*B;
	if(R[t]<n)
		t++,L[t]=R[t-1]+1,R[t]=n;
	for(int i=1; i<=t; i++)
		for(int j=L[i]; j<=R[i]; j++)
			pos[j]=i;
	
	for(int i=1; i<=t; i++)
	{
		for(int j=i; j<=t; j++)
		{
			int tmp=s[i][j-1];
			for(int k=L[j]; k<=R[j]; k++)
			{
				T[a[k]]++;
				if(T[a[k]]&1 && T[a[k]]!=1)
					tmp--;
				else if(T[a[k]]%2==0)
					tmp++;
			}
			s[i][j]=tmp;
		}
		
		for(int j=L[i]; j<=n; j++)
			T[a[j]]--;
	}
	
	for(int i=1; i<=t; i++)
	{
		for(int j=1; j<=c; j++)	
			cnt[j][i]=cnt[j][i-1];
		for(int j=L[i]; j<=R[i]; j++)
			cnt[a[j]][i]++;
	}
}

int ask(int l,int r)
{
	int p=pos[l],q=pos[r];
	if(p==q)
	{
		int res=0;
		for(int i=l; i<=r; i++)
		{
			T[a[i]]++;
			if(T[a[i]]%2==0)
				res++;
			else if(T[a[i]]!=1)
				res--;
		}
		for(int i=l; i<=r; i++)
			T[a[i]]--;
		return res;
	}
	
	int res=s[p+1][q-1];
	for(int i=l; i<=R[p]; i++)
	{
		T[a[i]]++;
		if((T[a[i]]+cnt[a[i]][q-1]-cnt[a[i]][p])%2==0)
			res++;
		else if(T[a[i]]+cnt[a[i]][q-1]-cnt[a[i]][p]!=1) 
			res--;
	}
	for(int i=L[q]; i<=r; i++)
	{
		T[a[i]]++;
		if((T[a[i]]+cnt[a[i]][q-1]-cnt[a[i]][p])%2==0)
			res++;
		else if(T[a[i]]+cnt[a[i]][q-1]-cnt[a[i]][p]!=1)
			res--;
	}
	
	for(int i=l; i<=R[p]; i++)
		T[a[i]]--;
	for(int i=L[q]; i<=r; i++)
		T[a[i]]--;
	
	return res;
}

int main()
{
	scanf("%d%d%d",&n,&c,&m);
	for(int i=1; i<=n; i++)
		scanf("%d",&a[i]);
	
	prework(); 
	
	int last=0;
	while(m--)
	{
		int l,r;
		scanf("%d%d",&l,&r);
		l=(l+last)%n+1;  r=(r+last)%n+1;
		if(l>r)
			swap(l,r);
		
		last=ask(l,r);
		printf("%d\n",last);
	}

	return 0;
}

P4168 [Violet] 蒲公英

题意:求区间众数,强制在线

同上题一样的操作,将 s[i][j] 改成表示块 ij 的众数是什么

code
#include<bits/stdc++.h>
using namespace std;

const int N=40010,BB=210;

int n,m,a[N],b[N],rk[N];
int T[N],cnt[N][BB],s[BB][BB]; 
int B,t,L[BB],R[BB],pos[N];

void prework()
{
	B=sqrt(n);  t=n/B;
	for(int i=1; i<=t; i++)
		L[i]=(i-1)*B+1,R[i]=i*B;
	if(R[t]<n)
		t++,L[t]=R[t-1]+1,R[t]=n;
	for(int i=1; i<=t; i++)
		for(int j=L[i]; j<=R[i]; j++)
			pos[j]=i;
	
	for(int i=1; i<=t; i++)
	{
		for(int j=i; j<=t; j++)
		{
			s[i][j]=s[i][j-1];
			for(int k=L[j]; k<=R[j]; k++)
			{
				T[a[k]]++;
				if(T[a[k]]==T[s[i][j]] && a[k]<s[i][j])
					s[i][j]=a[k];
				else if(T[a[k]]>T[s[i][j]])
					s[i][j]=a[k];
			}
		}
		for(int j=L[i]; j<=n; j++)
			T[a[j]]--;
	}
	
	for(int i=1; i<=t; i++)
	{
		for(int j=1; j<=n; j++)
			cnt[j][i]=cnt[j][i-1];
		for(int j=L[i]; j<=R[i]; j++)
			cnt[a[j]][i]++;
	}
}

int ask(int l,int r)
{
	int p=pos[l],q=pos[r],mx=0;
	if(p==q)
	{
		for(int i=l; i<=r; i++)
		{
			T[a[i]]++;
			if(T[a[i]]==T[mx] && a[i]<mx)
				mx=a[i];
			else if(T[a[i]]>T[mx])
				mx=a[i];
		}
		for(int i=l; i<=r; i++)
			T[a[i]]--;
		
		return mx;
	}
	
	mx=s[p+1][q-1];
	for(int i=l; i<=R[p]; i++)
	{
		T[a[i]]++;
		if(T[a[i]]+cnt[a[i]][q-1]-cnt[a[i]][p]==T[mx]+cnt[mx][q-1]-cnt[mx][p] && a[i]<mx)
			mx=a[i];
		else if(T[a[i]]+cnt[a[i]][q-1]-cnt[a[i]][p]>T[mx]+cnt[mx][q-1]-cnt[mx][p])
			mx=a[i];
	}
	for(int i=L[q]; i<=r; i++)
	{
		T[a[i]]++;
		if(T[a[i]]+cnt[a[i]][q-1]-cnt[a[i]][p]==T[mx]+cnt[mx][q-1]-cnt[mx][p] && a[i]<mx)
			mx=a[i];
		else if(T[a[i]]+cnt[a[i]][q-1]-cnt[a[i]][p]>T[mx]+cnt[mx][q-1]-cnt[mx][p])
			mx=a[i];
	}
	
	for(int i=l; i<=R[p]; i++)
		T[a[i]]--;
	for(int i=L[q]; i<=r; i++)
		T[a[i]]--;
	
	return mx;	
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; i++)
		scanf("%d",&a[i]),rk[i]=a[i];
		
	sort(rk+1,rk+1+n);
	int nn=unique(rk+1,rk+1+n)-(rk+1);
	
	for(int i=1; i<=n; i++)
	{
		int x=lower_bound(rk+1,rk+1+nn,a[i])-rk;
		b[x]=a[i];  a[i]=x;
	} 
		
	prework();
	
	int last=0;
	while(m--)
	{
		int l,r;
		scanf("%d%d",&l,&r);
		l=(l+last-1)%n+1;  r=(r+last-1)%n+1;
		if(l>r)
			swap(l,r);
		
		last=b[ask(l,r)];
		printf("%d\n",last);
	}

	return 0;
}

P5048 [Ynoi2019 模拟赛] Yuno loves sqrt technology III

题意:求区间众数出现的次数,要求空间 O(n)

将上题的 s[i][j] 改成块 ij 众数的出现次数

空间的瓶颈在于 cnt[x][i] 这个数组,考虑它的作用是什么。帮助我们判断散块里的数的贡献。

我们可以拿个 vector 记录下每个数的出现位置。询问时先取出 tmx=s[p+1][q1]。然后在枚举散块时,判断这个数往后再添加 tmx 个是否超出区间限制,暴力 tmx++ 即可

这样暴力加的操作次数最多为 O(B),所以时间复杂度就是 O(mn)

code
#include<bits/stdc++.h>
using namespace std;

const int N=500010,BB=810;

int n,m,a[N],rk[N],id[N];
int T[N],s[BB][BB]; 
int B,t,L[BB],R[BB],pos[N];
vector <int> cnt[N];

void prework()
{
	B=sqrt(n);  t=n/B;
	for(int i=1; i<=t; i++)
		L[i]=(i-1)*B+1,R[i]=i*B;
	if(R[t]<n)
		t++,L[t]=R[t-1]+1,R[t]=n;
	for(int i=1; i<=t; i++)
		for(int j=L[i]; j<=R[i]; j++)
			pos[j]=i;
	
	for(int i=1; i<=t; i++)
	{
		for(int j=i; j<=t; j++)
		{
			s[i][j]=s[i][j-1];
			for(int k=L[j]; k<=R[j]; k++)
			{
				T[a[k]]++;
				if(T[a[k]]>=s[i][j])
					s[i][j]=T[a[k]];
			}
		}
		for(int j=L[i]; j<=n; j++)
			T[a[j]]--;
	}
	
	for(int i=1; i<=n; i++)
		cnt[a[i]].push_back(i),id[i]=cnt[a[i]].size()-1;
}

int ask(int l,int r)
{
	int p=pos[l],q=pos[r],mx=0,tmx=0;
	if(p==q)
	{
		for(int i=l; i<=r; i++)
		{
			T[a[i]]++;
			if(T[a[i]]>=tmx)
				tmx=T[a[i]];
		}
		
		for(int i=l; i<=r; i++)
			T[a[i]]--;
		
		return tmx;
	}
	
	tmx=s[p+1][q-1];  
	
	for(int i=l; i<=R[p]; i++)
		while(id[i]+tmx<cnt[a[i]].size() && cnt[a[i]][id[i]+tmx]<=r)
			tmx++;
	for(int i=L[q]; i<=r; i++)
		while(id[i]-tmx>=0 && cnt[a[i]][id[i]-tmx]>=l)
			tmx++;
	
	return tmx;	
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; i++)
		scanf("%d",&a[i]),rk[i]=a[i];
		
	sort(rk+1,rk+1+n);
	int nn=unique(rk+1,rk+1+n)-(rk+1);
	
	for(int i=1; i<=n; i++)
		a[i]=lower_bound(rk+1,rk+1+nn,a[i])-rk;
		
	prework();
	
	int last=0;
	while(m--)
	{
		int l,r;
		scanf("%d%d",&l,&r);
		l^=last;  r^=last;
		if(l>r)
			swap(l,r);
		
		last=ask(l,r);
		printf("%d\n",last);
	}

	return 0;
}

P5046 [Ynoi2019 模拟赛] Yuno loves sqrt technology I

题意:求区间逆序对数,强制在线

无语了 32pts 不想写放在这

code
#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N=100010,BB=410;

int n,m,a[N];
int pre[N],suf[N],cnt[N][BB],tmp[BB][BB],tmpp[2][BB]; 
LL s[BB][BB];
int B,t,L[BB],R[BB],pos[N];

struct BIT
{
	int c[N];
	
	void add(int x,int y)
	{
		for(x; x<=n; x+=(x&-x))
			c[x]+=y;
	}
	
	int query(int x)
	{
		int res=0;
		for(x; x; x-=(x&-x))
			res+=c[x];
		return res;
	}
}tree;

int msort1(int l,int r)
{
	int res=0,i=1,j=1;
	while(i<=B && j<=B)
	{
		if(tmp[l][i]<tmp[r][j])
			i++;
		else
			res+=B-i+1,j++;
	}
	return res;
}

int msort2()
{
	int res=0,i=1,j=1;
	while(i<=tmpp[0][0] && j<=tmpp[1][0])
	{
		if(tmpp[0][i]<tmpp[1][j])
			i++;
		else
			res+=tmpp[0][0]-i+1,j++;
	}
	return res;
}

int calc(int l,int r,int flag)
{
	if(!flag)
		return msort1(l,r);
	else
		return msort2();
}

void prework()
{
	B=sqrt(n);  t=n/B;
	for(int i=1; i<=t; i++)
		L[i]=(i-1)*B+1,R[i]=i*B;
	if(R[t]<n)
		t++,L[t]=R[t-1]+1,R[t]=n;
	for(int i=1; i<=t; i++)
		for(int j=L[i]; j<=R[i]; j++)
			pos[j]=i;
	
	for(int i=1; i<=t; i++)
	{
		for(int j=L[i]; j<=R[i]; j++)
		{
			if(j!=L[i])
				pre[j]=pre[j-1];
			pre[j]+=j-L[i]-tree.query(a[j]);
			tree.add(a[j],1);
		}
		for(int j=L[i]; j<=R[i]; j++)
			tree.add(a[j],-1);
		for(int j=R[i]; j>=L[i]; j--)
		{
			suf[j]+=suf[j+1]+tree.query(a[j]);
			tree.add(a[j],1);
		}
		for(int j=R[i]; j>=L[i]; j--)
			tree.add(a[j],-1);
			
//			cout<<i<<"\n";
//		for(int j=L[i]; j<=R[i]; j++)
//			cout<<j<<":"<<pre[j]<<" "<<suf[j]<<endl;
	}
	
	for(int i=1; i<=t; i++)
	{
		for(int j=L[i]; j<=R[i]; j++)
			cnt[a[j]][i]++;
	}
	for(int i=1; i<=t; i++)
	{
		for(int j=1; j<=n; j++)
			cnt[j][i]+=cnt[j-1][i];
		for(int j=1; j<=n; j++)
			cnt[j][i]+=cnt[j][i-1];
	}
	
	for(int i=1; i<=t; i++)
	{
		for(int j=L[i]; j<=R[i]; j++)
			tmp[i][++tmp[i][0]]=a[j];
		sort(tmp[i]+1,tmp[i]+1+B);
	}
	
	for(int i=1; i<=t; i++)
		s[i][i]=pre[R[i]];//,cout<<i<<" "<<s[i][i]<<endl;
	for(int len=2; len<=t; len++)
	{
		for(int i=1; i+len-1<=t; i++)
		{
			int j=i+len-1;
			s[i][j]=s[i+1][j]+s[i][j-1]-s[i+1][j-1]+(LL)calc(i,j,0);
			
//			cout<<i<<" "<<j<<" : "<<s[i][j]<<" "<<calc(i,j,0)<<endl;
		}
	}
}

LL ask(int l,int r)
{
	int p=pos[l],q=pos[r];
	if(p==q)
	{
		LL res=0;
		for(int i=r; i>=l; i--)
		{
			res+=(LL)tree.query(a[i]);
			tree.add(a[i],1);
		}
		for(int i=l; i<=r; i++)
			tree.add(a[i],-1);
		
		return res;
	}
	
//	return 0;
	
	LL res=s[p+1][q-1]+suf[l]+pre[r];
	
// 	cout<<s[p+1][q-1]<<" "<<suf[l]<<" "<<pre[r]<<endl;
	
	tmpp[0][0]=tmpp[1][0]=0;
	for(int i=l; i<=R[p]; i++)
	{
		int tmp=cnt[a[i]][q-1]-cnt[a[i]][p];
		res+=(LL)tmp;
		tmpp[0][++tmpp[0][0]]=a[i];
	}
// 	cout<<res<<endl;
	for(int i=L[q]; i<=r; i++)
	{
		int tmp=cnt[a[i]][q-1]-cnt[a[i]][p];
// 		cout<<"kkk"<<a[i]<<" "<<tmp<<endl;
		res+=1LL*((q-p-1)*B-tmp);
		tmpp[1][++tmpp[1][0]]=a[i];
	}
	
	sort(tmpp[0]+1,tmpp[0]+1+R[p]-l+1);
	sort(tmpp[1]+1,tmpp[1]+1+r-L[q]+1);
// 	cout<<res<<endl<<tmpp[0][1]<<" "<<tmpp[1][1]<<" "<<tmpp[1][2]<<endl;
	res+=(LL)calc(0,1,1);
	return res;
}

signed main()
{
//	freopen("1.in","r",stdin);
//	freopen("1.out","w",stdout);
	
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; i++)
		scanf("%d",&a[i]);
		
	prework();
	
	LL last=0;
	for(int i=1; i<=m; i++)
	{
		int l,r;
		scanf("%d%d",&l,&r);
		l^=last;  r^=last;
		if(l>r)
			swap(l,r);
// 		cout<<l<<" "<<r<<endl; 
		last=ask(l,r);
		printf("%lld\n",last);
	}

	return 0;
}

P3396 哈希冲突

题意:给定长度为 n 的序列 a,要求支持:

  • 单点修改
  • 查询 i=1n[imodx=y]ai

根号分治入门题

根号复杂度会产生于一些非常自然的问题中

xn 时,modx 相同的数不超过 n 个,因此可以暴力查询

对于 x<n 的询问,每次单点修改对不同的 x 都会有一个 y 的答案改变,同样 O(n) 修改即可

code
#include<bits/stdc++.h>
using namespace std;

const int N=150010,BB=410;

int n,m,B,a[N];
int ans[BB][BB];

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; i++)
		scanf("%d",&a[i]);
	
	B=sqrt(n);
	for(int i=1; i<=n; i++)
		for(int j=1; j<B; j++)
			ans[j][i%j]+=a[i];
	
	while(m--)
	{
		char op[2];  int x,y;
		scanf("%s%d%d",op,&x,&y);
		
		if(op[0]=='A')
		{
			if(x>=B)
			{
				int res=0;
				for(int i=y; i<=n; i+=x)
					res+=a[i];
				printf("%d\n",res);
			}
			else
				printf("%d\n",ans[x][y]);
		}
		else 
		{
			for(int i=1; i<B; i++)
				ans[i][x%i]+=y-a[x];
			a[x]=y;
		}
	} 

	return 0;
}

P5309 [Ynoi2011] 初始化

题意:给定长度为 n 的序列,要求支持

  • 对所有 modx 等于 y 的数加 z
  • 求区间 [l,r] 的和

由于跳着加,首先考虑根号分治。

x>n 时,被修改的位置不超过 n 个,可以直接暴力修改。使用 O(1) 修改,O(n) 查询的分块

xn 时,设 f[x][y] 表示 modx 等于 y 的数被加了多少,但这样记状态查询时是 O(n) 的。考虑优化,枚举 x[1,n],要求的就是区间 [l,r] 有多少 modx 等于 0x1 的数,乘上对应的 f[x][y] 再求和。注意到一个 x 将整个序列分成了若干个长度为 x 的块加上末尾的小块(从 0 开始),如果一个块被 [l,r] 完全包含,那贡献就是 y=0xf[x][y],否则就是一段前缀和或者一段后缀和,拿前缀和优化一下就可以了

注意卡常,开 long long 最后再取模

code
#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N=200010,BB=500;
const int MOD=1e9+7;

int n,m;
int B,t,L[BB],R[BB],pos[N];
LL a[N],s[BB],sum[BB][BB];

void prework()
{
	B=sqrt(n);  t=n/B;
	for(int i=1; i<=t; i++)
		L[i]=(i-1)*B+1,R[i]=i*B;
	if(R[t]<n)
		t++,L[t]=R[t-1]+1,R[t]=n;
	for(int i=1; i<=t; i++)
		for(int j=L[i]; j<=R[i]; j++)
			pos[j]=i;
	
	for(int i=1; i<=t; i++)
		for(int j=L[i]; j<=R[i]; j++)
			s[i]+=a[j];
}

LL ask(int l,int r)
{
	int p=pos[l],q=pos[r]; LL res=0;
	if(p==q)
	{
		for(int i=l; i<=r; i++)
			res+=a[i];
		return res;
	}
	
	for(int i=p+1; i<=q-1; i++)
		res+=s[i];
	for(int i=l; i<=R[p]; i++)
		res+=a[i];
	for(int i=L[q]; i<=r; i++)
		res+=a[i];
	return res;
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; i++)
		scanf("%lld",&a[i]);
	
	prework();
	
	while(m--)
	{
		int op,x,y,z,l,r;
		scanf("%d",&op);
		
		if(op==1)
		{
			scanf("%d%d%d",&x,&y,&z);  y%=x;
			if(x>B)
				for(int i=y; i<=n; i+=x)
					a[i]+=z,s[pos[i]]+=z;
			else
				for(int i=y; i<x; i++)
					sum[x][i]+=z;
		}
		else
		{
			scanf("%d%d",&l,&r);
			LL res=ask(l,r);
			for(int i=1; i<=B; i++)	
			{
				res+=1LL*sum[i][i-1]*(r/i-l/i);
				res+=sum[i][r%i]-(l%i? sum[i][l%i-1]:0);
			}
			printf("%lld\n",res%MOD);
		}
	} 

	return 0;
}
posted @   xishanmeigao  阅读(13)  评论(0编辑  收藏  举报
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示