POJ 3349 重复的雪花(哈希)

题目链接: POJ3349 重复的雪花

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.

每个雪花都有六个分支,用六个整数代表,这六个整数是从任意一个分支开始,朝顺时针或逆时针方向遍历得到的。输入多个雪花,判断是否有形状一致的雪花存在。简单的数字哈希,要注意的是每种雪花可以由多种数字组合表示。
比如输入的是1 2 3 4 5 6,则2 3 4 5 6 1,3 4 5 6 1 2,……,6 5 4 3 2 1,5 4 3 2 1 6等都是相同形状的。

大致题意:
在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 <algorithm>
using namespace std;

const int N = 1200010;
const int H = 1200007;

struct Node
{
    int num[6];
    int next;
};
Node node[N];
int cur;
int hashTable[H];

void initHash()
{
    cur = 0;
    for (int i = 0; i < H; ++i) hashTable[i] = -1;
}

unsigned int getHash(int* num)
{
    unsigned int hash = 0;
    for (int i = 0; i < 6; ++i)
    {
        hash += num[i];
    }
    return hash % H;
}

bool cmp(int* num1, int* num2)
{
    for (int i = 0; i < 6; ++i)
    {
        if (num1[i] != num2[i]) return false;
    }
    return true;
}

void insertHash(int* num, unsigned int h)
{
    for (int i = 0; i < 6; ++i) node[cur].num[i] = num[i];
    node[cur].next = hashTable[h];
    hashTable[h] = cur;
    ++cur;
}

bool searchHash(int* num)
{
    unsigned h = getHash(num);
    int next = hashTable[h];
    while (next != -1)
    {
        if (cmp(num, node[next].num)) return true;
        next = node[next].next;
    }
    insertHash(num, h);
    return false;
}

int main()
{
    int num[2][12];
    int n;
    bool twin = false;
    initHash();
    scanf("%d", &n);
    while (n--)
    {
        for (int i = 0; i < 6; ++i)
        {
            scanf("%d", &num[0][i]);
            num[0][i + 6] = num[0][i];
        }
        if (twin) continue;
        for (int i = 0; i < 6; ++i)
        {
            num[1][i + 6] = num[1][i] = num[0][5 - i];
        }
        for (int i = 0; i < 6; ++i)
        {
            if (searchHash(num[0] + i) || searchHash(num[1] + i))
            {
                twin = true;
                break;
            }
        }
    }
    if (twin) printf("Twin snowflakes found.\n");
    else printf("No two snowflakes are alike.\n");
    return 0;
}

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;  
  
const __int64 prime=999983;  // 10n内最大的素数(本题n=10W)  
  
class  
{  
public:  
    __int64 len[6];  //6瓣叶子的长度  
}leaf[100001];  
  
typedef class HashTable  
{  
public:  
    __int64 len[6];   //记录6瓣叶子的长度信息  
    HashTable* next;  //用于冲突时开放寻址  
  
    HashTable()  //Initial  
    {  
        next=0;  
    }  
}Hashtable;  
  
Hashtable* hash[prime+1];  
  
/*计算第k个雪花的关键字key*/  
  
__int64 compute_key(int k)  
{  
    __int64 key=0;  
    for(int i=0;i<6;i++)  
    {  
        key+=(leaf[k].len[i]) % prime;  
        key%=prime;   //利用同余模定理计算key,避免出现大数  
    }  
  
    return ++key;  //键值后移1位,把key的范围从0~999982变为 1~999983  
}  
  
/*从顺时针方向判断两片雪花是否相同*/  
  
bool clockwise(Hashtable* p,int k)  
{  
    for(int j=0;j<6;j++)  //顺时针转动j格  
    {  
        bool flag=true;  
        for(int i=0;i<6;i++)  
            if(leaf[k].len[i] != p->len[(i+j)%6])  
            {  
                flag=false;  
                break;  
            }  
        if(flag)  
            return true;  
    }  
    return false;  
}  
  
/*从逆时针方向判断两片雪花是否相同*/  
  
bool counterclockwise(Hashtable* p,int k)  
{  
    for(int j=0;j<6;j++)  //逆时针转动j格  
    {  
        bool flag=true;  
        for(int i=0;i<6;i++)  
            if(leaf[k].len[i] != p->len[(5-i-j+6)%6])  
            {  
                flag=false;  
                break;  
            }  
        if(flag)  
            return true;  
    }  
    return false;  
}  
  
/*把第k个雪花信息插入HashTable*/  
/*当插入的位置已存在其他雪花信息时,顺便比较*/  
  
bool insert(int k)  
{  
    __int64 key=compute_key(k);  
  
    if(!hash[key])  
    {  
        Hashtable* temp=new Hashtable;  
  
        for(int i=0;i<6;i++)  
            temp->len[i]=leaf[k].len[i];  
  
        hash[key]=temp;  //保存key对应的地址  
    }  
    else  //地址冲突,开放寻址,顺便比较  
    {  
        Hashtable* temp=hash[key];  
  
        if(clockwise(temp,k) || counterclockwise(temp,k))  //检查雪花是否相同  
            return true;  
  
        while(temp->next)    //寻址  
        {  
            temp=temp->next;  
  
            if(clockwise(temp,k) || counterclockwise(temp,k))  //检查雪花是否相同  
                return true;  
        }  
  
        temp->next=new Hashtable;    //申请空间,保存新雪花信息  
        for(int i=0;i<6;i++)  
            temp->next->len[i]=leaf[k].len[i];  
    }  
    return false;  
}  
  
int main(int i,int j)  
{  
    int n;  //雪花数  
    while(cin>>n)  
    {  
        /*Initial*/  
  
        memset(hash,0,sizeof(hash));  // 0 <-> NULL  
  
        /*Input*/  
  
        bool flag=false;  //记录输入过程中是否出现了相同的雪花  
        for(i=1;i<=n;i++)  
        {  
            for(j=0;j<6;j++)  
                scanf("%I64d",&leaf[i].len[j]);  
  
            /*Hash*/  
  
            if(!flag)    //当前还没有出现相同的雪花  
                flag=insert(i);  
                         //若出现相同的雪花,则还需后续输入,但不再处理  
        }  
  
        /*Output*/  
  
        if(flag)  
            cout<<"Twin snowflakes found."<<endl;  
        else  
            cout<<"No two snowflakes are alike."<<endl;  
  
    }  
    return 0;  
} 



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值