To_Heart—题解——Hash 键值
今天,带大家看一看一道思维题。。。
Hash 键值 (hash)
题目描述
Marser沉迷hash无法自拔,然而他发现自己记不住hash键值了……
Marser使用的hash函数是一个单纯的取模运算,每一个数i被对应到i mod p。他现在有一个数列,他采用这种方法,把每一个数对应到一个键值i mod p。他想知道对于给定的模数p和键值r,所有对应到该键值的数的和为多少。同时,Marser可能会发现他的数列出了一些问题,所以他还想随时更改数列中任意一项的值。
现在Marser有q个请求,每个请求可能是修改或是询问。对于每一个询问,你需要给出正确的答案。如果你不能在1s内正确回答所有询问,Marser就会让hotwords把你给续了。
输入格式
从文件hash.in读入。
第一行两个整数n,q,表示数列长度和请求数量。
第二行n个整数,表示初始的序列。
接下来m行,每行三个整数opt,x,y;
若opt=1,则询问在mod x时,所有对应到键值y的数的和。
若opt=2,则将数列第x项修改为y。
输出格式
输出到文件hash.out中。
样例
样例输入 1
10 5
1 2 3 4 5 6 7 8 9 10
1 2 1
2 1 20
1 3 1
2 5 1
1 5 0
样例输出 1
25
41
11
首先,我们发现这是一道水题
轻轻松松写下下面的代码
#include <bits/stdc++.h>
using namespace std;
int a[100005];
int main() {
int n, m;
freopen("hash.in","r",stdin);
freopen("hash.out","w",stdout);//文件输入输出
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}
for (int i = 1; i <= m; ++i) {
int x;
scanf("%d", &x);
if (x == 1) {
int x1, x2, ans = 0;
scanf("%d%d", &x1, &x2);
for (int i = x2; i <= n; i += x1) { //不需要一个一个枚举,只需要算x1的倍数就行了;
ans += a[i];
}
printf("%d\n", ans);
}
if (x == 2) {
int x1, x2;
scanf("%d%d", &x1, &x2);
a[x1] = x2; //维护
}
}
}
然后我们发现:
轻轻松松超时
竟然可以拿30分!!!
咳咳,言归正传,我们开始讨论100分的代码
(开始吟唱)
Frist:
我们需要算一下时间复杂度
原代码时间复杂度为O(n+mn/x1);
所以我们的代码一定要小于这个时间复杂度;
second
我们发现,无论如何原代码的时间复杂度是减少不了的了至少我没想出啦。。。,所以只能:
另辟蹊径:
仔细一想,不难得出:如果我们能在x1,x2读入的时候,就直接输出ans,那么时间复杂度肯定会大大降低;
那么我们怎么才可以实现这种操作呢?
首先,定义一个名为sum的二维数组,sum[i][j]表示所有模i余j的数的总和;
再输入一个a[i]以后,就用一重循环遍历,代码如下:
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
for(int j=1;j<n;++j){
sum[j][i%j]+=a[i]; //除j模(i%j);
}
}
这样,我们其实就可以达成输入就输出啦!!!
但我们再来算算时间复杂度为O ( n 2 ) (n^2) (n2)
然后我们神奇的发现,时间复杂度竟然比原来还大!
那么,如果我们不遍历所有值,而是只遍历log(n),再把x1>log(n)的情况用第一种方法运算呢???
再算算时间复杂度:O= ( n log 2 m + max ( m n / x 1 , m n ) ) (n\log_2^m+\max(mn/x1,mn)) (nlog2m+max(mn/x1,mn));
化简一下:O= ( n log 2 m + m n x 1 ) (n\log_2^m+\frac {mn}{x1}) (nlog2m+x1mn);
然后我们就可以过剩下的测试点啦!!!
上代码!!!:
#include<bits/stdc++.h>
using namespace std;
int a[1000005],sum[1005][1005]; //sum[i][j]等于所有除以i余j的数的和;
int main(){
freopen("hash.in","r",stdin);
freopen("hash.out","w",stdout); //文件输入输出
int n,m;
scanf("%d%d",&n,&m);
int num=sqrt(n);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
for(int j=1;j<num;j++){
sum[j][i%j]+=a[i]; //对sum进行赋值,循环j模拟i除以的数之和;
}
}
for(int i=1;i<=m;++i){
int x;
scanf("%d",&x);
if(x==1){
int x1,x2,ans=0;
scanf("%d%d",&x1,&x2);
if(x1<num){//如果x1<num,说明x1在sum处理的范围内;
printf("%d\n",sum[x1][x2]); //直接通过前文对sum的处理输出;
}
else{
for(int i=x2;i<=n;i+=x1){ //方法一;
ans+=a[i];
}
printf("%d\n",ans);
}
}
if(x==2){
int x1,x2;
scanf("%d%d",&x1,&x2);
for(int i=1;i<=num;i++)
sum[i][x1%i]=sum[i][x1%i]-a[x1]+x2; //维护sum数组,把a[x1]覆盖成x2,并确保sum里的值正确;
a[x1]=x2; //对a[i]也要进行维护;
}
}
}