《算法竞赛进阶指南》 雪花雪花雪花

有 NN 片雪花,每片雪花由六个角组成,每个角都有长度。

第 ii 片雪花六个角的长度从某个角开始顺时针依次记为 ai,1,ai,2,…,ai,6ai,1,ai,2,…,ai,6。

因为雪花的形状是封闭的环形,所以从任何一个角开始顺时针或逆时针往后记录长度,得到的六元组都代表形状相同的雪花。

例如 ai,1,ai,2,…,ai,6ai,1,ai,2,…,ai,6 和 ai,2,ai,3,…,ai,6,ai,1ai,2,ai,3,…,ai,6,ai,1 就是形状相同的雪花。

ai,1,ai,2,…,ai,6ai,1,ai,2,…,ai,6 和 ai,6,ai,5,…,ai,1ai,6,ai,5,…,ai,1 也是形状相同的雪花。

我们称两片雪花形状相同,当且仅当它们各自从某一角开始顺时针或逆时针记录长度,能得到两个相同的六元组。

求这 NN 片雪花中是否存在两片形状相同的雪花。

输入格式

第一行输入一个整数 NN,代表雪花的数量。

接下来 NN 行,每行描述一片雪花。

每行包含 66 个整数,分别代表雪花的六个角的长度(这六个数即为从雪花的随机一个角顺时针或逆时针记录长度得到)。

同行数值之间,用空格隔开。

输出格式

如果不存在两片形状相同的雪花,则输出:

No two snowflakes are alike.

如果存在两片形状相同的雪花,则输出:

Twin snowflakes found.

数据范围

1≤N≤1000001≤N≤100000,
0≤ai,j<100000000≤ai,j<10000000

输入样例:

2
1 2 3 4 5 6
4 3 2 1 6 5

输出样例:

Twin snowflakes found.

先补充一个函数:

补充一个函数sort(a, a + n, cmp); 


bool cmp(int a, int b)
{
    if (a < b) return true:(返回的是a序列从小到大排序)
}
bool cmp(int a, int b)
{
    if (a > b) return true:(返回的是a序列从大到小排序)
}
 

解题思路:

每次求出每个序列的最小字典序,若每个最小字典序列中存在相同的序列则说明存在相同的雪花
如何求得最小字典序呢:
{
    方法一:1:将每个序列的最前一位依次移动到最后一个位置,比如一个长度为4的序列:
    1,2,3,4移动后2,3,4,1直到序列变为4,3,2,得到每次变化过程中的序列的某一次的最小的字典序;
    
    方法二:求翻转序列:将一个序列翻转,比如一个长度为4的序列5, 2, 3, 4翻转后即为4, 3, 2, 5;
    
    将前面两种方法结合即:先求翻转序列然后求原序列,以及翻转序列,每次将首位元素移动到末尾元素的最小字典序
    
    最后将翻转序列的最小字典序与原序列的最小字典序比较,得出的答案即为该序列的最小字典序
    
    求最小字典序的方法:
    {
        可以定义一个长度为2 * n的序列记录的是由原序列(即为b[N])复制过来长度是原序列两倍的序列(即为a[N]):
        例如原序列a[N]为5, 2, 3, 4的b[N * 2]为5, 2, 3, 4, 5, 2, 3, 4;
        然后将b[N * 2]分为:
        b1[N * 2]:5, 2, 3, 4, 5, 2, 3, 4;
        b2[N * 2]:5, 2, 3, 4, 5, 2, 3, 4;
        最后用双指针依次枚举两个数组,找出最小字典序;
    }
    

解题代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010;

int n;
int snow[6], resnow[6];//snow为原数组,resnow为翻转数组
int snows[N][6], idx[N];//snows记录的是每片雪花的最小字典序,idx用来将 snows排序

void get_min(int b[])
{
    static int a[12];
    for (int i = 0; i < 12; i ++ ) a[i] = b[i % 6];//把b变为长度是2 * len(b)的数组
    
    int i = 0, j = 1, k;//i为b1开始枚举的位置,j为b2开始枚举的位置,k为枚举的长度
    while (i < 6 && j < 6)//不能大于6,若大于6则不是一个完整的序列
    {
        for (k = 0; k < 6 && a[i + k] == a[j + k]; k ++ );//当他们当前比较的数字相等时两个指针同时往后移动一位
        if (k == 6) break;//数组最大长度为6,若k == 6说明找到了最小字典序
        
        if (a[i + k] > b[j + k])//说明从数组a前面从第i位数到第i + k位数中所对应的每个长度位n的序列都大于b
        {//这与找最小字典序不符,所以a应跳到第k位数后面继续枚举序列,即i += k + 1;
            i += k + 1;
            if (i == j) i ++ ;//因为是相同的数组,指针不能在一个时刻指向同一个位置。
        }
        else 
        {
            j += k + 1;//同理
            if (i == j) j ++ ;
        }
    }
    
    k = min(i, j);//循环结束说明i, j中有一个大于6,要找出i, j中小于6的即求i, j最小值
    for (int i = 0; i < 6; i ++ ) b[i] = a[i + k];
}

bool cmp2(int a, int b)
{
    for (int i = 0; i < 6; i ++ )
        if (snows[a][i] < snows[b][i])
            return true;
        else if (snows[a][i] > snows[b][i])
                return false;
    
    return false;
}

bool cmp(int a[], int b[])
{
    for (int i = 0; i < 6; i ++ )
        if (a[i] < b[i])
            return true;
        else if (a[i] > b[i])
                return false;
    
    return false;
}

int main()
{
    cin >> n;
    for (int i = 0; i < n; i ++ )
    {
        for (int j = 0, k = 5; j < 6; j ++, k -- ) 
        {
            scanf("%d", &snow[j]);//记录原数组与翻转数组
            resnow[k] = snow[j];
        }
        
        get_min(snow);//得到最小字典序
        get_min(resnow);
        
        if (cmp(snow, resnow)) memcpy(snows[i], snow, sizeof snow);
        else memcpy(snows[i], resnow, sizeof resnow);
        //比较原数组与翻转数组最小字典序的大小,将最小字典序最小的存下来
        idx[i] = i;
    }
    
    sort(idx, idx + n, cmp2);//将所有snows从小到大排序
    
    bool is_same = false;
    for (int i = 1; i < n; i ++ )
        if (!cmp2(idx[i - 1], idx[i]) && !cmp2(idx[i], idx[i - 1]))
        //若idx[i - 1] >= idx[i] && idx[i] >= idx[i - 1]则两者相等
        {
            is_same = true;
            break;
        }
    
    if (is_same) puts("Twin snowflakes found.");
    else puts("No two snowflakes are alike.");
    
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

啥也不会hh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值