题目链接:https://www.acwing.com/problem/content/139/
题意:有N片雪花,每片雪花由六个角组成,每个角都有长度。
第i片雪花六个角的长度从某个角开始顺时针依次记为ai,1,ai,2,…,ai,6。因为雪花的形状是封闭的环形,所以从任何一个角开始顺时针或逆时针往后记录长度,得到的六元组都代表形状相同的雪花。
如果不存在两片形状相同的雪花,则输出:
No two snowflakes are alike.
如果存在两片形状相同的雪花,则输出:
Twin snowflakes found.
数据范围
1≤n≤100000,
0≤ai,j<10000000
输入样例:
2
1 2 3 4 5 6
4 3 2 1 6 5
输出样例:
Twin snowflakes found.
思路:我们可以对序列进行两种操作:
- 旋转:将左侧第一个数去掉加在最右侧,例如原本的312旋转一次变成123,
- 翻转:将整个序列完全翻转一下,例如原本的123翻转过后就是321
那么我们可以对一个序列去最小值,即旋转和翻转结合得到所能得到的最小序列;那么把所有序列都处理成最小值后有就可以直接判断是否有相同的了。
但问题就在于这个最小化,一般的暴力做法是O(n²)的,这就需要用到一下双指针算法了。
代码实现:
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 1e5 + 5;
int n;
int snows[N][6], idx[N];
//比较两个序列的大小,a<b即为真
bool cmp_array(int a[], int b[])
{
for(int i = 0; i < 6; i ++ )
if(a[i] > b[i])
return 0;
else if(a[i] < b[i])
return 1;
return 0;
}
//返回snows[a]<snows[b]
bool cmp(int a, int b)
{
return cmp_array(snows[a], snows[b]);
}
void get_min(int a[])
{
//设立一个静态数组b
static int b[12];
for(int i = 0; i < 12; i ++ )
b[i] = a[i % 6];
int i = 0, j = 1, k;
while(i < 6 && j < 6){
for(k = 0; k < 6 && b[i + k] == b[j + k]; k ++ )
if(k == 6) break;
//如果找到不相等的数就直接跳过前面k个数,这样两重循环下来最大O(2n)
if(b[i + k] > b[j + k]){
i += k + 1;
if(i == j) i ++ ;
}
else{
j += k + 1;
if(i == j) j ++ ;
}
}
k = min(i, j);
for(i = 0; i < 6; i ++ ) a[i] = b[i + k];
}
int main()
{
cin >> n;
//snow和isnow一个正序一个逆序
int snow[6], isnow[6];
for(int i = 0; i < n; i ++ ){
for(int j = 0, k = 5; j < 6; j ++, k -- ){
cin >> snow[j];
isnow[k] = snow[j];
}
//分别取正逆序的min序列
get_min(snow);
get_min(isnow);
//将得到的最小序列复制进答案序列组中
if(cmp_array(snow, isnow))
memcpy(snows[i], snow, sizeof snow);
else memcpy(snows[i], isnow, sizeof isnow);
idx[i] = i;
}
sort(idx, idx + n, cmp);
int tmp = 0;
//snows[i]不小于snows[i - 1],且snows[i - 1]也不小于snows[i],即两个序列是相等的
for(int i = 1; i < n; i++ )
if(!cmp(idx[i - 1], idx[i]) && !cmp(idx[i], idx[i - 1])){
tmp = 1;
break;
}
if(tmp) cout << "Twin snowflakes found." << endl;
else cout << "No two snowflakes are alike." << endl;
return 0;
}