POJ-3349-Snowflake Snow Snowflakes

Snowflake Snow Snowflakes
Time Limit: 4000MS Memory Limit: 65536K
Total Submissions: 39430 Accepted: 10326

Description

You may have heard that no two snowflakes are alike. Your task is to write a program to determine whether this is really true. Your program will read information about a collection of snowflakes, and search for a pair that may be identical. Each snowflake has six arms. For each snowflake, your program will be provided with a measurement of the length of each of the six arms. Any pair of snowflakes which have the same lengths of corresponding arms should be flagged by your program as possibly identical.

Input

The first line of input will contain a single integer n, 0 < n ≤ 100000, the number of snowflakes to follow. This will be followed by n lines, each describing a snowflake. Each snowflake will be described by a line containing six integers (each integer is at least 0 and less than 10000000), the lengths of the arms of the snow ake. The lengths of the arms will be given in order around the snowflake (either clockwise or counterclockwise), but they may begin with any of the six arms. For example, the same snowflake could be described as 1 2 3 4 5 6 or 4 3 2 1 6 5.

Output

If all of the snowflakes are distinct, your program should print the message:
No two snowflakes are alike.
If there is a pair of possibly identical snow akes, your program should print the message:
Twin snowflakes found.

Sample Input

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

Sample Output

Twin snowflakes found.

Source


转载请注明出处:優YoU  http://user.qzone.qq.com/289065406/blog/1304831877

大致题意:

在n (n<100000)个雪花中判断是否存在两片完全相同的雪花,每片雪花有6个角,每个角的长度限制为1000000

 两片雪花相等的条件:

雪花6个角的长度按顺序相等(这个顺序即可以是顺时针的也可以是逆时针的)

 

解题思路:

Hash吧!连加求余法 求key 值,链地址法解决冲突

设雪花6片叶子的长度为len1~len6

key=( len1+len2+len3+len4+len5+len6)%prime

   =( len1%prime +len2%prime +len3%prime +len4%prime +len5%prime +len6)%prime

为了避免出现大数,这里使用了同余模定理求key

 

注意,这里的key千万不能用平方和,本来这题时间就很紧凑了,乘法运算更加严重浪费时间,所以采用了连加求key,只要prime足够大,同样能够把地址冲突降低到最低,我取了10n(就是100W)内的最大的素数作为prime,   prime=999983

 

基本做法:

从上面的处理手法能够知道:

当且仅当两片雪花的key值一样时,这两片雪花才有可能相同。

 

在输入第k个雪花信息的时候,先求出其key值,若在hash[]中key值没有出现过,则直接存放信息。但如果在hash[key]中已经保存有其他地址,说明此前出现过key值相同的其他雪花,这些雪花信息以链表的形式存放在hash[key]中,这时在为第k个雪花信息寻找存放空间的同时,必然在链表中逐一访问这些具有相同key值得雪花,所以我们就能在寻址的同时,顺便逐一比较第k个雪花与这些雪花的信息,一旦发现k与某个雪花是一样的,则标记,然后等待后续输入完成后,直接输出寻找到两片一样的雪花。

 

但是当所有输入结束后都没有标记过,则说明不存在一样的雪花。

 

这时肯定又会有同学有疑问:

Key值只能说明两片雪花的叶子长度之和相等,但是不能说明6片叶子分别相等,更加不能说明6片叶子按顺序相等。那么当我们寻找到key值相同的两片雪花时,我们该如何比较两片雪花?

其实是可以的。假设有两片雪花,A 和B

我们固定A,先按顺时针方向比较

若A0==B0,则按顺序比较A1和B1.........比较A5和B5

只要当出现Ai != Bi,则把B顺时针转动一次,

若A0==B1,则按顺序比较A1和B2.........比较A5和B0

。。。。。

以此类推,直至B转动了5次,若还不相同,则说明这两片雪花在顺时针方向不等。

 

再比较逆时针方向

同样固定A,若A0==B5,则按顺序比较A1和B4.........比较A5和B0

只要当出现Ai != B(5-i),则把B逆时针转动一次,

若A0==B4,则按顺序比较A1和B3.........比较A5和B5

。。。。。

以此类推,直至B转动了5次,若还不相同,则说明这两片雪花在逆时针方向不等。

 

如是者,比较两片雪花最坏的情况为要比较36*2 = 72 次!!!

可想而知当n=10W时,若任意两片比较,则最坏要比较 72*(10W)^2次

 

在这里给出两条公式:

设i为A、B的第i片叶子,j为B当前顺时针转过的格数

那么  A(i) ---> B( (i+j)%6 )

设i为A、B的第i片叶子,j为B当前逆时针转过的格数

那么  A(i) ---> B( (5-i-j+6)%6 )

 

因此,为了尽可能第降低比较次数,那么我们就需要把雪花按key值分类,此时就务求prime在恰当的范围内尽可能大,使得地址冲突 (出现两个或以上key值相同的雪花) 的情况尽可能降到最低,最理想的情况就是:当且仅当两片雪花是相同的时候,他们的key值才相等。那么根据前面的算法思路(只对key值相同的两片雪花进行比较),在最理想情况下,我们最多仅需比较1次就能得到“存在雪花相同”结果,最少比较0次就能得到“不存在一样的雪花”的结果。

 

经过测试发现,prime取100W左右的素数时,key的离散程度是相对比较高的,冲突也就很少,prime再大,对离散化程度影响不大,而且会浪费空间。。

而当prime取10W左右的素数时,出现key值相同的情况达到6K多个,此时Hash的优势根本体现不了。

 



#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#define prime 999983
using namespace std;


struct node
{
    int len[6];
}leaf[100001];


typedef struct HashTable
{
    int len[6];
    HashTable *next;
}HashTable;


HashTable *Hash[prime+1];


int get_Key(int k)
{
    int key = 0;
    for(int i = 0; i < 6; i++)
    {
        key += (leaf[k].len[i])%prime;
        key %= prime;
    }


    return ++key;
}


/*顺时针判断两片雪花是否形同*/


bool clowkwise(HashTable *H,int k)
{
    for(int j = 0; j < 6; j++)
    {
        bool flag = true;
        for(int i = 0; i < 6; i++)
        {
            if(leaf[k].len[i] != H->len[(i+j)%6])
            {
                flag = false;
                break;
            }
        }
        if(flag)
            return true;
    }
    return false;
}


/*逆时针判断两片雪花是否形同*/


bool counterclowkwise(HashTable *H,int k)
{
    for(int j = 0; j < 6; j++)
    {
        bool flag = true;
        for(int i = 0; i < 6; i++)
        {
            if(leaf[k].len[i] != H->len[(5-i-j+6)%6])
            {
                flag = false;
                break;
            }
        }
        if(flag)
            return true;
    }
    return false;
}


/*把第K个雪花信息插入HashTable*/
/*如果位置有元素则比较*/


bool Insert(int k)
{
    int key = get_Key(k);


    if(!Hash[key])//地址不冲突
    {
        HashTable *temp = new HashTable;
        temp->next = NULL;


        for(int i = 0; i < 6; i++)
            temp->len[i] = leaf[k].len[i];


        Hash[key] = temp;//保存key对应的地址
    }
    else//地址冲突
    {
        HashTable *temp = Hash[key];


        if(clowkwise(temp,k) || counterclowkwise(temp,k))
            return true;


        while(temp->next)//遍历地址冲突的哈希元素
        {
            temp = temp->next;


            if(clowkwise(temp,k) || counterclowkwise(temp,k))
                return true;
        }


        temp->next = new HashTable;
        temp->next->next=NULL;
        for(int i = 0; i < 6; i++)//雪花不同保存新的雪花信息
            temp->next->len[i] = leaf[k].len[i];
    }
    return false;
}


int main()
{
    int n;
    while(~scanf("%d",&n))
    {
            int i,j;
            memset(Hash,0,sizeof(Hash));//初始化


            bool flag = false;//标记记录是否有相同的雪花出现
            for(i = 1; i <= n; i++)
            {
                for( j = 0; j < 6; j++)
                    scanf("%d",&leaf[i].len[j]);


                if(!flag)
                    flag = Insert(i);
            }


            if(flag)
                printf("Twin snowflakes found.\n");
            else
                printf("No two snowflakes are alike.\n");
    }
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值