今天第二次模拟赛,题目仍然是很基础的,比如这道,即是乱搞。写这道题主要练习了一下哈希最基础的应用:模大质数。(当然这道题可以不用哈希来写)
题目链接:http://oi.nks.edu.cn/showproblem?problem_id=1982
这道题如果暴力枚举是O(n^3)的,而如果我们想办法把它变成O(n^2)即可。
思路就是:我们可以维护每两个数的和,O(n^2)做到;之后再枚举第三个数j和答案i,i要逆序,判断两个数的和中是否存在i-j,O(n^2)做到。
但要实现,是有许多问题需要注意的:
1.我们要判断i-j是否存在,所以维护的每两个数的和只需要标记一下即可,但是由于数据大小我们并开不了这么大的bool型数组。有两种解决方法:把所有的和排序,二分查找i-j是否存在;用哈希,模一个大质数后,就可以开得下数组了。为了更快我选的后者。
2.记录和的同时还要记录是哪两个数的和,因为这两个数不能和i,j重合,这里用结构体比较方便。
3.用哈希必然会出现冲突,所以我们需要用到链表。这样的话用结构体就更加方便,其中只需要三个变量,表示两个数,以及链表指针(数组模拟)指向的下一个元素。
4.对于负数的情况,需要再加一个我们所模的大质数让它变成正数,C语言没有负下标,这样顶多也就是可能多几个链。
5.由于这个哈希本质是同余,所以会出现一些错误的情况,只需要加一句特判即可。(如四个数5,7,9,8,5+7+9>8,但是在mod13下,5+7+9=8)所加的特判就是,在哈希之后的数判断存在i-j(也是需要mod P的)这个数存在后,取出组成这个和的那两个数,用他们原来的值加和判断是否等于i-j原来的值即可。
小吐槽一下:在网上搜题解搜到了两份,一份是pascal写的哈希的,并看不懂,写C的总觉得pascal的很乱,然而它过不了;还有一份是C语言,用二分写的,仍然过不了,应该是出了以上的小问题。自己也算是巩固了一下哈希的应用以及细节的处理。
#include <cstdio>
#include <algorithm>
#include <cstring>
#define P 1000003
using namespace std;
int n, a[1001], hash[1000005];
struct node{
int a, b, nex;
}t[1000005];
int d;
bool jud(int q, int a, int b){
return t[q].a == a || t[q].b == a || t[q].b == a || t[q].b == b;
}
int main()
{
int N = 2;
while(N--){
d = 0;
memset(hash, 0, sizeof hash);
memset(t, 0, sizeof t);
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%d", a+i);
sort(a+1, a+n+1);
for(int i = 1; i < n; i++)
for(int j = i+1; j <= n; j++){
int k = (a[i]+a[j]) % P;
if(k < 0) k += P;
if(!hash[k]) {
hash[k] = ++d;
t[d].a = i, t[d].b = j;
}else{
d++;
int q = hash[k];
while(t[q].nex) q = t[q].nex;
t[q].nex = d;
t[d].a = i, t[d].b = j;
}
}
int ans = n, flag = 1;
for(; ans && flag; ans--)
for(int i = n; i; i--){
if(i == ans) continue;
int k = (a[ans]-a[i]) % P;
if(k < 0) k += P;
if(!hash[k]) continue;
int q = hash[k];
while(jud(q, i, ans) && t[q].nex) q = t[q].nex;
if(!jud(q, i, ans) && q){
if(a[t[q].a] + a[t[q].b] + a[i] != a[ans]) continue;
flag = 0;
printf("%d\n", a[ans]);
break;
}
}
if(flag) puts("No Solution");
}
}