luogu 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]。每次更改立即生效。 保证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.

解答

其实这道题数据比较水暴力模拟91分。暴力对抗随机数据好像比较强。暴力代码:

// luogu-judger-enable-o2
# pragma GCC optimize("Ofast")
# include <cstdio>
inline int read(){
    char ch = getchar();
    int f = 1 ,x = 0;
    while(ch > '9' || ch < '0'){if(ch == '-')f = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + ch - '0';ch = getchar();}
    return x * f;
}
const int MAXN =  150000;
int a[MAXN];
long long F[400][400];
int main(void){
    int n,m;
    n = read(),m = read();
    for(int i = 1;i<=n;i++){
        a[i] = read();
    }
    char c;
    int x,y;
    int ans = 0;
    char s[3];
    while(m--){
        scanf("%s",s);
        if(s[0]=='A'){
            x = read(),y = read();
            ans = 0;
            for(int i = y;i<=n;i+=x){
                ans+=a[i];
            }
            printf("%d\n",ans);
        }else{
            x = read();
            y = read();
            a[x] = y;
        }
    }
    return 0;
}

但是我们还是来想正解吧。正解是分块优雅的暴力 它非常的骚。分块有如下特点

就是对于一定数据范围内(一般取$\sqrt n$),我们把它打表打出来,然后$O(1)$查询。又或者直接把一组数据分成$\sqrt n (+1)$块,每块$\lfloor \sqrt(n)\rfloor$个数据,然后直接乱搞.—–沃·镃基硕·德

我们把n分成两部分:小于$\sqrt(n)$的一部分,其余的一部分。然后对于小于根号n的部分。为什么呢?如果我们预处理全部的部分,那么查询复杂度是$O(n^2)$的。如果我们预处理出F[i][j]表示%i意义下,结果为j的值得和是多少,这样预处理复杂度也是$O(n^2)$的,虽然这样查询是$O(1)$。对于这种查询和预处理/修改复杂度差别比较大的或者差不多但是都不够理想的(例如树状数组维护单点或者区间修改的前缀和),我们通常用分块来做乱搞 于是乎对于这道题,我们预处理$\sqrt n$的部分,其余的直接暴力(暴力:你是不是看不起我???)至于修改,我们要把预处理的数组和原数组都同时修改了,修改代码如下

x = read();
y = read();
for(int i = 1;i*i<=n;i++){
    F[i][x%i]+=y-a[x];
}
a[x] = y;

注意,在读入’A’和’C’的时候,不可以getchar() 因为这样会把换行符和回车读进来,要读入成一个字符串! AC代码:

// luogu-judger-enable-o2
# pragma GCC optimize("Ofast")
# include <cstdio>
inline int read(){
    char ch = getchar();
    int f = 1 ,x = 0;
    while(ch > '9' || ch < '0'){if(ch == '-')f = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + ch - '0';ch = getchar();}
    return x * f;
}
const int MAXN =  150000;
int a[MAXN];
long long F[400][400];
int main(void){
    int n,m;
    n = read(),m = read();
    for(int i = 1;i<=n;i++){
        a[i] = read();
        for(int j = 1;j*j<=n;j++){
            F[j][i%j] += a[i];
        }
    }
    char s[3];
    int x,y;
    while(m--){
        scanf("%s",s);
        if(s[0]=='A'){
            x = read(),y = read();
            if(x*x<=n){
                printf("%d\n",F[x][y]);
            }else{
                int ans = 0;
                for(int i = y;i<=n;i+=x){
                    ans += a[i];
                }
                printf("%d\n",ans);
            }
        }else{
            x = read();
            y = read();
            for(int i = 1;i*i<=n;i++){
                F[i][x%i]+=y-a[x];
            }
            a[x] = y;
        }
    }
    return 0;
}