题目来源:http://noi.openjudge.cn/ch0305/1551/
1551: Sumsets
总时间限制: 1000ms 内存限制: 65536kB
描述
Given S, a set of integers, find the largest d such that a + b +c = d where a, b, c, and d are distinct elements of S.
输入
Several S, each consisting of a line containing an integer 1<= n <= 1000 indicating the number of elements in S, followed by theelements of S, one per line. Each element of S is a distinct integer between-536870912 and +536870911 inclusive. The last line of input contains 0.
输出
For each S, a single line containing d, or a single linecontaining "no solution".
样例输入
5
2
3
5
7
12
5
2
16
64
256
1024
0
样例输出
12
no solution
来源
Waterloo local 2001.06.02
-----------------------------------------------------
思路
如果直接枚举a,b,c再用d去二分查找,则复杂度为O(n^3*logn),会超时。故改造表达式为
a + b = d – c
分别枚举a,b和c,d,再二分查找,复杂度为O(n^2*logn)。
进一步优化,用空间换时间,开一个巨大的数组,数组长度等于a+b可能的上界-下界,二分查找的过程也免去了,复杂度为O(n^2).
但是这样空间开销太大,且这个数组会十分稀疏,因为实际上a+b的取值最多只有n*(n-1)/2种可能。故采用哈希算法。开4个数组:
1. 数组s为哈希数组,下标为哈希值,数组元素为a+b的实际值。用open addressing的方式处理collision(即发生访问冲突则线性增加索引,直到不冲突为止。open addressing适用于负载因子较低的hash table)。哈希算法采用很朴素的把a+b全部转化为非负数再对数组s大小取模
2. 数组b1下标为哈希值,数组元素为a
3. 数组b2下标为哈希值,数组元素为b. 设b1,b2两个数组主要是因为题目要求等式中a,b,c,d互不相同
4. 数组flag,下标为哈希值,true表示该哈希值的s数组有元素了,否则false. flag的意义在于在hash table中查找d-c数发现s[h]!=d-c时, 区别到底是没有hash(a+b)==h(不用找了)还是hash(a+b)==h发生了collision(需要继续往下找)。每组数据计算完毕要清空flag
编写3个函数:
1. myhash: 输入整数,计算hash值
2. add: 把a+b的结果存入hash table
3. myfind: 在hash table里查找d-c的值
还有一个技巧是把输入数据排序,遍历d-c的时候按d从大到小的顺序遍历,找到的第一个d就是题目要求的最大的d
-----------------------------------------------------
代码
#include<iostream> #include<fstream> #include<algorithm> #include<cstring> using namespace std; const int NMAX = 1005; // 集合中元素最多的个数 const int MYMIN = 536870912; // 所有输入+MYMIN可以变成非负数, %运算的时候就不会出现负数使数组下标越界 const int RANGE = NMAX*NMAX; // 哈希表的长度,负载因子在0.5左右 int in[NMAX] = {}; // 集合中的元素 int s[RANGE] = {}; // a+b的哈希, 用open_addressing来处理collision bool flag[RANGE] = {}; // 哈希表中是否有数据 int b1[RANGE] = {}; // a+b中的a int b2[RANGE] = {}; // a+b中的b int myhash(int n) // hash算法, 根据a+b的结果n计算hash值h { int h = (n+2*MYMIN)%RANGE; while (flag[h]) { h = (h+31)%RANGE; } return h; } void add(int a, int b) // 把a+b放入哈希表s, 同时用b1,b2记录a,b { int h = myhash(a+b); s[h] = a+b; b1[h] = a; b2[h] = b; flag[h] = true; return; } bool myfind(int d, int c) // 在哈希表中查找d-c是否存在, 且a,b,c,d不能重复 { int h = (d-c+2*MYMIN)%RANGE; while (flag[h]) { if (s[h]==d-c && !(b1[h]==d || b2[h]==c || b1[h]==c || b2[h]==d)) { return true; } else { h = (h+31)%RANGE; } } return false; } int main() { #ifndef ONLINE_JUDGE ifstream fin ("0305_1551.txt"); int n,i,j,a,b,c,d; bool is_found = false; while (fin >> n) { if (n==0) { break; } if (n<4) { cout << "no solution" << endl; continue; } memset(flag,0,sizeof(flag)); // 每轮计算过后要清空flag,否则会发生collision的误判 is_found = false; for (i=0; i<n; i++) { fin >> in[i]; } sort(in, in+n); // 将集合中的元素升序排列,这样从后往前遍历d-c的时候自然就能找到最大的d for (i=1; i<n; i++) { for (j=0; j<i; j++) { a = in[i]; b = in[j]; add(a,b); } } for (i=n-1; i>=0; i--) { for (j=n-1; j>=0; j--) { if (i!=j) { d = in[i]; c = in[j]; if (myfind(d,c)) { is_found = true; cout << d << endl; break; } } } if (is_found) { break; } } if (!is_found) { cout << "no solution" << endl; } } fin.close(); #endif #ifdef ONLINE_JUDGE int n,i,j,a,b,c,d; bool is_found = false; while (cin >> n) { if (n==0) { break; } if (n<4) { cout << "no solution" << endl; continue; } memset(flag,0,sizeof(flag)); is_found = false; for (i=0; i<n; i++) { cin >> in[i]; } sort(in, in+n); // 将集合中的元素升序排列,这样从后往前遍历d-c的时候自然就能找到最大的d for (i=1; i<n; i++) { for (j=0; j<i; j++) { a = in[i]; b = in[j]; add(a,b); } } for (i=n-1; i>=0; i--) { for (j=n-1; j>=0; j--) { if (i!=j) { d = in[i]; c = in[j]; if (myfind(d,c)) { is_found = true; cout << d << endl; break; } } } if (is_found) { break; } } if (!is_found) { cout << "no solution" << endl; } } return 0; #endif }