AcWing 137 雪花雪花雪花

题目描述:

有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片雪花中是否存在两片形状相同的雪花。

输入格式

第一行输入一个整数N,代表雪花的数量。接下来N行,每行描述一片雪花。每行包含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.

分析:

做这题前看别人说时间空间卡的很紧,所以迟迟没有下手,结果用笨办法一股脑刷刷写完,竟然一遍ac了,也觉得很惊奇。

在介绍本题的解法之前,先引入字符串的最小表示法的概念。我们需要求一个字符串的循环同构体中最小字典序的那个。比如bcda的循环同构体有bcda,cdab,dabc,abcd,其中最小表示法是abcd。

那么如何高效的求解字符串的最小表示法呢?显然,暴力求解的话就是依次枚举字符串的起点,再与其他位置作为起点的字符串一一对比,总的时间复杂度是O(n ^2)。以abcdabcebdd为例详细描述下暴力做法的过程。

定义双指针i = 0,j = 1,首先a < b所以字符串的第二个位置不会作为最优解字符串的开头,同理,后面的cd也都不会作为字符串的开头,直到j = 4匹配到了另一个a,于是i,j均右移,发现后面的bc也都相等,继续右移,直至遇见了d < e,所以abce不可能是最小表示法的前缀,然后j又回到下标为5的位置,i也回到开头,a < b,继续比较。

上面的匹配过程可以采用类似于KMP算法的思想来优化,一句话概括就是遇见不可能匹配的前缀就快速跳过,不再进行比较。在比较到abcd和abce时,发现不匹配了,于是i指针还是回到起点,而j指针不必再回到abce中的b位置进行下一次匹配,因为一旦abce丧失了作为字符串真前缀的资格,其子串也都失去了作为前缀的资格。比如说bce有作为前缀的资格,则由之前的比较可知i指针指向的一个前缀bcd是比该前缀小的,不论是abce的哪个子串,在abcd中都能够找到字典序小于它的子串。所以:

abcdabcebdd

abcebddabcd

两个循环同构体在d和e的位置失配时,第一个指针重新回到下标为0的位置,而第二个指针不需要在从下标为0的位置的后一个位置再进行比较,既然abce都失去了前缀的资格,那么第二个指针从第五个字符b再次开始比较即可,如此,指针便可快速右移。

总结下最小表示法的求解思路:

i,j分别指向待匹配两个字符串的首位置,k表示已匹配的长度,一旦当前的位置匹配,则k++,否则,字典序较大的那个字符串的指针则要右移k + 1位才能再次进行比较。最小表示法代码如下:

#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
int findMin(string s) {
	int len = s.size();
	int i = 0, j = 1, k = 0;
	while (i < len && j < len && k < len) {
		int a = (i + k) % len, b = (j + k) % len;
		if (s[a] == s[b])	k++;
		else if (s[a] > s[b]) {
			i += k + 1;
			k = 0;
		}
		else
		{
			j += k + 1;
			k = 0;
		}
		if (i == j)	j++;
	}
	return min(i, j);
}
int main() {
	int n;
	string s;
	cin >> n;
	while (n--) {
		cin >> s;
		cout << findMin(s) + 1 << endl;
	}
	return 0;
}

 说完了最小表示法,正式说下本题解题思路。本题需要判断雪花是否相同,只要两片雪花的六个角的最小表示法相同,我们就可以断定这两片雪花完全相同,所不同的是:上面介绍的最小表示法只能自左往右循环,而本题可以自右向左循环,只需读入时备份一下逆置的数组,然后仅保留两个数组的最小表示即可。

至于如何剪枝来避免不必要的比较,可以使用简单的哈希来解决。我们知道,两片雪花相同的充要条件就是六个角最小表示法相同,如果对任意两片雪花的六个角求和,则当两片雪花的六角和不等时,他们一定不相同。考虑到本题的数据范围,在求和映射时需要取模,一般取接近于最大范围的最大的素数,我这里取的是9999991。

综上所述:本题的主要思路就是,先对所有雪花都哈希映射到某个值,只有两片雪花散列后的值相等时他们才可能相等,再比较他们的最小表示法即可验证。

#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;
typedef long long ll;
const int maxn = 100005;
int a[maxn][6],b[maxn][6],p[maxn];
unordered_map<int,vector<int> > m;
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 true;
}
bool issame(int *a,int *b){
    for(int i = 0;i < 6;i++){
        if(a[i] != b[i])    return false;
    }
    return true;
}
void getmin(int *a){
    int i = 0,j = 1,k = 0;
    while(i < 6 && j < 6 && k < 6){
        int t1 = (i + k) % 6,t2 = (j + k) % 6;
        if(a[t1] == a[t2])   k++;
        else if(a[t1] < a[t2]){
            j += k + 1;
            k = 0;
        }
        else{
            i += k + 1;
            k = 0;
        }
        if(i == j)  j++;
    }
    int s = min(i,j);
    k = 0;
    int b[6];
    while(k < 6)    b[k++] = a[(s++) % 6];
    for(int i = 0;i < 6;i++)    a[i] = b[i];
}
void getunique(int *a,int *b){
    getmin(a),getmin(b);
    if(!cmp(a,b)){
        for(int i = 0;i < 6;i++)    a[i] = b[i];
    }
}
int gethash(int *a){
    ll sum = 0;
    int mod = 9999991;
    for(int i = 0;i < 6;i++)    sum = (sum + a[i]) % mod;
    return sum;
}
int main(){
    int n;
    cin>>n;
    for(int i = 0;i < n;i++){
        for(int j = 0;j < 6;j++){
            cin>>a[i][j];
            b[i][5 - j] = a[i][j];
        }
        getunique(a[i],b[i]);
        m[gethash(a[i])].push_back(i);
    }
    for(auto x : m){
        int siz = x.second.size();
        if(siz > 1){
            for(int i = 0;i < siz;i++){
                    for(int j = i + 1;j < siz;j++){
                        if(issame(a[x.second[i]],a[x.second[j]])){
                         cout<<"Twin snowflakes found."<<endl;
                         exit(0);
                        }
                    }
                }
        }
    }
    cout<<"No two snowflakes are alike."<<endl;
    return 0;
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值