题意:
有 N
片雪花,每片雪花 由六个角组成,每个角都有长度。
第 i
片雪花 六个角的长度 从某个角开始顺时针依次记为 ai,1、ai,2、…、 ai,6
。
因为雪花的形状是封闭的环形,所以 从任何一个角开始顺时针或逆时针往后记录长度,得到的六元组都代表形状相同的雪花。
例如:ai,1、ai,2、…、ai,6
和 ai,2、ai,3、…、ai,6、ai,1
就是形状相同的雪花。ai,1、ai,2、…、ai,6
和 ai,6、ai,5、…、ai,1
也是形状相同的雪花。
我们定义 两片雪花形状相同,当且仅当它们 各自从某一个角开始顺时针或逆时针记录长度,能得到两个相同的六元组。
求 这 N
片雪花中 是否存在 两片形状相同 的雪花。
思路:
观察数据范围,我们应该将复杂度控制在 O(n)
或 O(nlogn)
的范围内,这里我们使用 O(n)
的哈希表做法。
先定义 Hash
函数
(其中 Р
是我们自己选取的一个较大的质数)
显然,对于两片形状相同的雪花,它们六个角的长度之和、长度之积都相等,因此它们的 Hash
函数值也相等,存储到 哈希表同一个表头下的链表 中(即 一个 Hash
值对应 一条链表)。
建立一个 Hash
表,把 N
片雪花依次插入。
对于 每片雪花 ai,1、ai,2、ai,6
,先用上方定义的 哈希函数 求出其对应的 哈希值 haval
,之后直接 以 O(1)
的复杂度 找到 表头 h(haval)
对应的链表,并 以 O(常数)
的复杂度 扫描该链表,检查是否存在与 ai,1、ai,2、…、ai,6
形状相同的雪花 ,如果存在则表明 有两片相同的雪花,反之则 将新的雪花存入当前扫描的链表。
如果 N
次插入的雪花之前都没有往哈希表中存入相同的,则判断为 不存在两片相同的雪花。
当 检查两片雪花是否形状相同 时,由于 每片雪花角的个数仅仅只有 6
,因此这里我们可以直接 暴力模拟判断。之后我们会学习 循环同构串 的 “最小表示法”,这个方法可以进一步提高判断两片雪花形状是否相同的 效率。
时间复杂度:
对于 随机数据,期望的时间复杂度为 O(N ^ 2 / P)
,P
取为 最接近 N
的质数,期望的时间复杂度为 O(N)
。
代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include <bits/stdc++.h>
using namespace std;
#define int long long
typedef vector<int> vi;
#define pb push_back
#define pp pop_back
const int N = 1e5 + 10, P = 99991, M = 10;
int n;
int a[M];
int h[N], e[N][M], ne[N], idx;
int H(int a[]) //哈希函数
{
int sum = 0, pw = 1;
for (int i = 0; i < 6; ++i)
{
sum = (sum + a[i]) % P;
pw = pw * a[i] % P;
}
return (sum + pw) % P;
}
bool isame(int a[], int b[]) //暴力模拟判断,注意根据题意,正反都要做一次
{
vi aa, bb;
for (int i = 0; i < 6; ++i) aa.push_back(a[i]);
for (int i = 0; i < 6; ++i)
{
bb.clear();
for (int j = i; j < 6; ++j) bb.push_back(b[j]);
if (bb.size() < 6)
{
int tmp = 6 - bb.size();
for (int i = 0; i < tmp; ++i) bb.push_back(b[i]);
}
if (aa == bb) return true;
}
aa.clear(), bb.clear();
reverse(a, a + 6);
for (int i = 0; i < 6; ++i) aa.push_back(a[i]);
for (int i = 0; i < 6; ++i)
{
bb.clear();
for (int j = i; j < 6; ++j) bb.push_back(b[j]);
if (bb.size() < 6)
{
int tmp = 6 - bb.size();
for (int i = 0; i < tmp; ++i) bb.push_back(b[i]);
}
if (aa == bb) return true;
}
return false;
}
bool find(int a[]) //查找哈希表中当前雪花对应表头的链表,看看是否之前出现过
{
int haval = H(a);
for (int i = h[haval]; ~i; i = ne[i])
{
if (isame(e[i], a)) return true; //出现过,直接返回为真
}
//未出现过,拉链法插入哈希表。并返回为假(类比之前链式前向星建图方式即可)
memcpy(e[idx], a, 6 * sizeof(int)), ne[idx] = h[haval], h[haval] = idx++;
return false;
}
signed main()
{
int T = 1; //cin >> T;
while (T--)
{
int n; cin >> n;
memset(h, -1, sizeof h);
int t = n;
while (t--)
{
for (int i = 0; i < 6; ++i) cin >> a[i];
if (find(a))
{
puts("Twin snowflakes found.");
return 0;
}
}
puts("No two snowflakes are alike.");
}
return 0;
}