P3396 哈希冲突 (根号算法)

题目链接:https://www.luogu.org/problemnew/show/P3396

题目描述

众所周知,模数的hash会产生冲突。例如,如果模的数p=7,那么411便冲突了。

B君对hash冲突很感兴趣。他会给出一个正整数序列value[]

自然,B君会把这些数据存进hash池。第value[k]会被存进(k%p)这个池。这样就能造成很多冲突。

B君会给定许多个px,询问在模p时,x这个池内数的总和

另外,B君会随时更改value[k]。每次更改立即生效。

保证1<=p<n1<=p<n.

输入输出格式

输入格式:

第一行,两个正整数n,m,其中n代表序列长度,m代表B君的操作次数。

第一行,n个正整数,代表初始序列。

接下来m行,首先是一个字符cmd,然后是两个整数x,y

  • cmd=‘A‘,则询问在模x时,y池内数的总和。
  • cmd=‘C‘,则将value[x]修改为y

输出格式:

对于每个询问输出一个正整数,进行回答。

输入输出样例

输入样例#1:

10 5
1 2 3 4 5 6 7 8 9 10
A 2 1
C 1 20
A 3 1
C 5 1
A 5 0

输出样例#1:

25
41
11

说明

样例解释

A 2 1的答案是1+3+5+7+9=25.

A 3 1的答案是20+4+7+10=41.

A 5 0的答案是1+10=11.

数据规模

对于10%的数据,有n<=1000,m<=1000.

对于60%的数据,有n<=100000.m<=100000.

对于100%的数据,有n<=150000,m<=150000.

保证所有数据合法,且1<=value[i]<=1000.



看了一下题,只会暴力,在分块专题里找的,疯狂找分块方案,还是不会QWQ,看大佬的题解,蒟蒻只能Orz,

这应该可以算是一个神奇的分块吧qwq,下面请看大佬的题解



这是一道论文题。集训队论文《根号算法——不只是分块》。

首先,题目要我们求的东西,就是下面的代码:

for(i=k;i<=n;i+=p)
    ans+=value[i];

即:从 k开始,每隔p个数取一个数,求它们的和。

这个算法的复杂度是O(n^2)O(n2)的。

令答案为ans[p][k]ans[p][k],表示模数是p,余数是k.

那么,对于第i个数,如何处理它对ans的贡献呢?

for(p=1;p<=n;p++) //枚举模数
    ans[p][i%p]+=value[i]; //处理对应的贡献

这样看上去很妙的样子,然而O(n^2)O(n2)的预处理, O(1)O(1)询问,空间复杂度还是

O(n^2)O(n2)的

所以我们很自然地想到:只处理[1,\sqrt{n}][1,n?]以内的p

这样的话,令 size=\sqrt{n}size=n?,则可以这样预处理:

for(p=1;p<=size;p++) //只枚举[1,size]中的
    ans[p][i%p]+=value[] //处理对应的贡献

于是预处理的复杂度降到了 O(n\sqrt{n})O(nn?).

接着考虑询问。如果询问的p<size ,那显然可以O(1)O(1)给出回答。

如果p超过size,我们就暴力统计并回答。因为 p>\sqrt{n}p>n?,所以少于\sqrt{n}n?个数对答案有贡献。所以对于 p>\sqrt{n}p>n?,暴力统计的复杂度是 O(\sqrt{n})O(n?)..

接着考虑修改。显然我们把p<size的值全都更新一遍就行。复杂度也是 O(\sqrt{n})O(n?).

void change(int i,int v) //将value[i]改为v
    {
    for(p=1;p<=size;p++)
    ans[p][i%p]=ans[p][i%p]-value[i]+v; //更新答案
    value[i]=v; //更新value数组
}

这样,我们就在O((m+n)\sqrt{n})O((m+n)n?).的时间内完成了任务

自己ac的代码

/**
 *        ┏┓    ┏┓
 *        ┏┛┗━━━━━━━┛┗━━━┓
 *        ┃       ┃  
 *        ┃   ━    ┃
 *        ┃ >   < ┃
 *        ┃       ┃
 *        ┃... ⌒ ...  ┃
 *        ┃       ┃
 *        ┗━┓   ┏━┛
 *          ┃   ┃ Code is far away from bug with the animal protecting          
 *          ┃   ┃   神兽保佑,代码无bug
 *          ┃   ┃           
 *          ┃   ┃        
 *          ┃   ┃
 *          ┃   ┃           
 *          ┃   ┗━━━┓
 *          ┃       ┣┓
 *          ┃       ┏┛
 *          ┗┓┓┏━┳┓┏┛
 *           ┃┫┫ ┃┫┫
 *           ┗┻┛ ┗┻┛
 */

#include <bits/stdc++.h>
#include <iostream>

using namespace std;

#define  ll long long
const int maxn=150000+5;
const int maxn1=1500+5;
bool cmp(){
    return 0;
}
int a[maxn];
int ans[maxn1][maxn1];
//set<int> a,b;
set<pair<int,int> > ps;
set<pair<int,int> > pt;

int n,q;
void init(){
    for(int i=1; i<=n; ++i)
        for(int p=1; p*p<=n; ++p)
        ans[p][i%p]+=a[i];
}

void updata(int x, int val){
    for(int p=1; p*p<=n; ++p){
        ans[p][x%p]+=(val-a[x]);
    }
    a[x]=val;
}

int main(){
    std::ios::sync_with_stdio (false);
    int flag=1;
    int x,y;
    cin>>n>>q;
    for(int i=1; i<=n; ++i) cin>>a[i];
    init();
    while(q--){
        string cmd;
        cin>>cmd>>x>>y;
        if(cmd=="A"){
            if(x*x<=n) cout<<ans[x][y]<<"\n";
            else {
                int ret=0;
                for(int i=y; i<=n; i+=x) ret+=a[i];
                cout<<ret<<"\n";
            }
        }
        else updata(x,y);
    }
    return 0;
}

原文地址:https://www.cnblogs.com/mrdushe/p/11160305.html

时间: 2024-10-10 00:50:31

P3396 哈希冲突 (根号算法)的相关文章

P3396 哈希冲突

题目背景 此题约为NOIP提高组Day2T2难度. 题目描述 众所周知,模数的hash会产生冲突.例如,如果模的数p=7,那么4和11便冲突了. B君对hash冲突很感兴趣.他会给出一个正整数序列value[]. 自然,B君会把这些数据存进hash池.第value[k]会被存进(k%p)这个池.这样就能造成很多冲突. B君会给定许多个p和x,询问在模p时,x这个池内数的总和. 另外,B君会随时更改value[k].每次更改立即生效. 保证. 输入输出格式 输入格式: 第一行,两个正整数n,m,其

luogu P3396 哈希冲突(分块?)

我们可以维护一个\(f[i][j]\)代表%\(i\)意义下得\(j\)的答案.然后维护就炸了. 先设\(x=\sqrt{n}\)然后我们发现,当\(i>x\)时我们直接暴力复杂度为\(O(x)\),然后我们对\(i\leq{x}\)的i维护\(f[i][j]\),这样询问复杂度\(O(1)\),维护复杂度\(O(x)\).就可以通过此题了. #include<iostream> #include<cstring> #include<cstdio> #includ

【CF103D】Time to Raid Cowavans [根号算法]

CF103D Time to Raid Cowavans 一个序列\(a\),\(m\)次询问,每次询问给出\(t,k\),求\(a_t+a_{t+k}+a_{t+2k}+...+a_{t+pk},t+(p+1)k>n\) 步长\(k\ge\sqrt n\)时暴力枚举 \(k<\sqrt n\)时预处理出来部分和\(O(n\sqrt n)\) 但是这样会MLE 所以用一个\(sum\)数组 将询问离线询问 \(<\sqrt n\)的\(k\)不会超过\(\sqrt n\)个 所以复杂度不

【数据结构】处理哈希冲突的开链法(哈希桶)算法实现

实现哈希表时,我们常见的方法是线性探测.二次探测,这两个算法也很简单.若有兴趣,可以查看我的博客.但是,这两个算法有一个共同点就是:空间利用率低.为什么这么说呢?线性探测.二次探测的高效性很大程度上要取决于它的载荷因子,载荷因子即:存放关键字个数/空间大小. 通过查阅资料,我发现,使用素数做除数可以减少哈希冲突(具体原因不详,大师专研的,发现很好用,就在这里分享给大家).见下: ----素数表 // 使用素数表对齐做哈希表的容量,降低哈希冲突 const int _PrimeSize = 28;

算法学习 - HashTable开放地址法解决哈希冲突

开放地址法解决哈希冲突 线性开放地址法 线性开放地址法就是在hash之后,当发现在位置上已经存在了一个变量之后,放到它下一个位置,假如下一个位置也冲突,则继续向下,依次类推,直到找到没有变量的位置,放进去. 平方开放地址法 平方地址法就是在hash之后,当正确位置上存在冲突,不放到挨着的下一个位置,而是放到第2^0位置,假如继续冲突放到2^1的位置,依次2^3... 直到遇到不冲突的位置放进去. 双散列开放地址法 双散列同上,不过不是放到2^的位置,而是放到key - hash(key, tab

算法学习 - Hash Table操作,分离链接法解决哈希冲突

分离链接法 hash table是映射机制的,最大的优点就是它的操作是O(1)级别的.但是会出现哈希冲突,这就需要几种办法来解决.这里先说一种:分离链接法. 就是当插入的位置已经存在一个值之后,那么在这个值之后插入,就可以了,也叫拉链法.(但是其实会降低查找速度,变成O(n)级别) 下面是代码: // // main.cpp // HashTable_SeparateChaining // // Created by Alps on 14-8-5. // Copyright (c) 2014年

算法6-3:解决哈希冲突之线性探针

线性探针是另外一种解决哈希冲突的办法.这种办法的基本思想就是当遇到哈希冲突时,寻找下一个空位,直到找到空位为止. 示例 先插入一个值S,如下图. 插入其他的一些值,这些值的哈系没有冲突,得到下图的结果. 再插入一个值H,由于H与A的哈系冲突,因此需要寻找一个空的位置. 找到了空位 插入 代码 public class LinearProbeST<Key, Value> { private static final int M = 100; private Key[] keys = (Key[]

哈希冲突的处理【闭散列方法-线性探测和二次探测】

散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构.也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度.这个映射函数叫做散列函数,存放记录的数组叫做散列表. 给定表M,存在函数Hash(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数Hash(key)为哈希(Hash) 函数. 构造哈希表的两种方法 1.直接定址法--取关键字的某个线性函数为散列地

处理哈希冲突的线性探测法

哈希表,是根据关键字(Key value)而直接访问在内存存储位置的数据结构.也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度.这个映射函数称做散列函数,存放记录的数组称做散列表.(摘自维基百科) 对不同的关键字可能得到同一散列地址,即k1!=k2,而f(k1)=f(k2),这种现象称为碰撞(英语:Collision),也叫哈希冲突. 处理哈希冲突的方法有很多种: 闭散列法 开链法(哈希桶) 素数表 字符串哈希算法 在这里我们讨论最简单的闭散