题解 洛谷 P3396 【哈希冲突】(根号分治)

根号分治

前言

本题是一道讲解根号分治思想的论文题(然鹅我并没有找到论文),正

如论文中所说,根号算法——不仅是分块,根号分治利用的思想和分块像

似却又不同,某一篇洛谷日报中说过,分块算法实质上是一种是通过分成

多块后在每块上打标记以实现快速区间修改,区间查询的一种算法。根号

分治与其思路相似,将原本若一次性解决时间复杂度很高的问题分块去解

决来降低整体的时间复杂度。

例题

以本题举例子哈希冲突

本题作为论文的第一道题目,是一道很好的练习题,注意,本体给出的

v a l u e [ i ] value[i] value[i] i i i 在序列中出现的次数,不要把题读错了(一开始我就读错了)

我们首先阅读题目,发现,无论是 O ( n 2 ) O(n^2) O(n2) 预处理, O ( 1 ) O(1) O(1) 查询,还是在

查询时直接 O ( n 2 ) O(n^2) O(n2)获取答案,都是 O ( n 2 ) O(n^2) O(n2) 的时间复杂度,我们考虑对

其进行优化,我们可以考虑将 p p p,也就是模数按根号分块处理

对于 p < = q p<=\sqrt{q} p<=q 我们直接 O ( n n ) O(n\sqrt{n}) O(nn ) 进行预处理,

	for(int i=1;i<=n;i++){
		v=read();
		val[i]=v;
	}
	size=sqrt(n); 
	for(int i=1;i<=n;i++){
		for(int p=1;p<=size;p++){
			ans[p][i%p]+=val[i]; 
		}
	}

a n s [ p ] [ i ] ans[p][i] ans[p][i] 表示在 %p 后值为 i i i的数的个数

p > q p>\sqrt{q} p>q 的情况,

我们直接暴力得出答案,暴力得到答案的时间复杂度为 O ( n ) O(\sqrt{n}) O(n )

	int an=val[y]; 
	for(int i=x+y;i<=n;i+=x){
		an+=val[i]; 
	}
	cout<<an<<endl;

a n = v a l [ y ] an=val[y] an=val[y] 是为了处理 y y%x=y y ( y < x ) (y<x) (y<x) 的情况,为什么说我们暴力跳

答案时间复杂度是 O ( n ) O(\sqrt{n}) O(n ) 呢?我们当前的 p > n ) p>\sqrt{n}) p>n ) 因为统

计答案时每一次要跳 p p p个数,所以有贡献的数一定小于 n p \frac{n} {p} pn 也就是 n \sqrt{n} n ,所以暴力得到答案的时间复杂度为 O ( n ) O(\sqrt{n}) O(n )

每一次修改,我们只需要修改 p < = n p<=\sqrt{n} p<=n 的情况即可,时间复杂度也是 O ( n ) O(\sqrt{n}) O(n )

	cin>>x>>y;
	int l=y-val[x];
	val[x]=y; 
	for(int p=1;p<=size;p++){
		ans[p][x%p]+=l;
	}

所以在本题,我们运用根号分治的想法,把时间复杂度由原本的 O ( n 2 ) O(n^2) O(n2)

优化到了 O ( n n ) O(n\sqrt{n}) O(nn ) 从而解决本题。

莫名觉得根号分治挺像折半搜索
,推荐一道练习题CF444D DZY Loves Strings
还是很有难度的

代码

放一下全部代码吧

#include<iostream>
#include<string>
#include<string>
#include<cstdio>
#include<cmath>
#define int long long
using namespace std;
const int maxn=3e5+10;
inline int read(){
	int ret=0;
	int f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')	
			f=-f;
		ch=getchar();
	}
	while(ch<='9'&&ch>='0'){
		ret=ret*10+(ch^'0');
		ch=getchar();
	}
	return ret*f;
}
int val[maxn];
int n,m;
int p;
int ans[2000][2000];
int size;
char a;
signed main(){
//	freopen("a.in","r",stdin);
	n=read();
	m=read();
	int v;
	for(int i=1;i<=n;i++){
		v=read();
		val[i]=v;//???????? 
	}
	size=sqrt(n);//???? 
	for(int i=1;i<=n;i++){
		for(int p=1;p<=size;p++){
			ans[p][i%p]+=val[i]; 
		}
	}
	int x,y;
	while(m--){
		cin>>a;
		if(a=='A'){
			x=read();
			y=read();
			if(x<=size){
				cout<<ans[x][y]<<endl;
			}
			else{
				int an=val[y]; 
				for(int i=x+y;i<=n;i+=x){
						an+=val[i]; 
				}
				cout<<an<<endl;
			} 
		}
		if(a=='C'){
			cin>>x>>y;
			int l=y-val[x];
			val[x]=y; 
			for(int p=1;p<=size;p++){
				ans[p][x%p]+=l;
			}
		}
	}
	return 0;
}

到这里本题解就结束了

完结撒花!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值