2018-10-14普及模拟赛」Hash 键值 (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 
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=(nn+mn)(为什么是mn呢?因为当我们更改一个值时,那么sum数组的之也要改);

然后我们神奇的发现,时间复杂度竟然比原来还大!

那么,如果我们不遍历所有值,而是只遍历log(n),再把x1>log(n)的情况用第一种方法运算呢???

再算算时间复杂度:O=(n*log(m)+MAX(mn/x1,mn));

化简一下:O=(n*log(m)+mn/x1);

然后我们就可以过剩下的测试点啦!!!
上代码!!!:

再次声明:如果你要抄代码,只会害了你自己。。。

#include
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=2;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
				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]也要进行维护; 
		}
	}
}

你可能感兴趣的:(思维,数据结构)