【洛谷6896】[ICPC2014 WF] Maze Reduction(哈希)
- 有\(n\)个房间,每个房间都是圆形,顺时针给出它连向的每个房间标号。
- 现除去所有标号,若从两个房间出发无法区分彼此,则二者等价,求所有大小超过\(1\)的等价类。
- \(n\le100\)
恶心的哈希
这题一个非常恶心的地方在于每个房间都是圆形,而边又是按顺时针给出,是存在相对顺序的。
所以说,如果我们沿着一条边走到一个房间,则房间中的边与入边之间的相对顺序是作为一个已知信息存在的。
因此我们记录\(f_{i,j,k}\)表示从\(i\)出发,选择了第\(j\)条边,走\(k\)步的哈希值。
显然从\(f_{G_{i,j},p,k-1}\)转移,\(p\)要从\(j\)在\(G_{i,j}\)中对应的编号\(w_{G_{i,j},i}\)开始枚一轮。由于有顺序,每加上一个新的转移值之前要先乘上一个\(seed1\)。
边界就是\(f_{i,j,0}=c_i+1\)(\(c_i\)表示\(i\)的边数)。
搞完之后,我们把每个\(f_{i,j,0\sim n}\)哈希一下,这里最好换一个种子\(seed2\)。
然后由于给定的是一个圆形,我们求出最小表示法,最好再换一个种子\(seed3\)。
最后只要看哪些房间哈希值相同,就是等价的了。
代码:\(O(n^4)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100
using namespace std;
int n,c[N+5],w[N+5][N+5],G[N+5][N+5];vector<int> S[N+5];vector<int>::iterator it;
struct Hash
{
#define ull unsigned long long
#define CU Con ull&
ull x,y;I Hash() {x=y=0;}I Hash(CU a):x(a),y(a){}I Hash(CU a,CU b):x(a),y(b){}
I Hash operator + (Con Hash& o) Con {return Hash(x+o.x,y+o.y);}
I Hash operator * (Con Hash& o) Con {return Hash(x*o.x,y*o.y);}
I bool operator < (Con Hash& o) Con {return x^o.x?x<o.x:y<o.y;}
I bool operator == (Con Hash& o) Con {return x==o.x&&y==o.y;}
I friend bool operator <= (Con Hash& A,Con Hash& B) {return A<B||A==B;}
}s1(324682339,456789001),s2(177777777,233333333),s3(71717171,23232323),f[N+5][N+5][N+5],H[N+5];
int cnt;map<Hash,int> id;I int ID(Con Hash& x) {return id.count(x)?id[x]:id[x]=++cnt;}
int main()
{
RI i,j,k;for(scanf("%d",&n),i=1;i<=n;++i) for(scanf("%d",c+i),
j=1;j<=c[i];++j) scanf("%d",G[i]+j),w[i][G[i][j]]=j,f[i][j][0]=c[i];//记录每条边的编号;初始化f[i][j][0]
RI o,p,q;for(k=1;k<=n;++k) for(i=1;i<=n;++i) for(j=1;j<=c[i];++j)//哈希值转移
{
for(p=q=w[o=G[i][j]][i];p<=c[o];++p) f[i][j][k]=f[i][j][k]*s1+f[o][p][k-1];//从当前边开始枚一轮
for(p=1;p^q;++p) f[i][j][k]=f[i][j][k]*s1+f[o][p][k-1];
}
Hash h;for(i=1;i<=n;++i)//求出每个房间的总哈希值
{
if(!c[i]) {S[ID(Hash())].push_back(i);continue;}//特判单点
for(j=1;j<=c[i];++j) for(H[j-1]=Hash(),k=0;k<=n;++k) H[j-1]=H[j-1]*s2+f[i][j][k];//每个f[i][j]先哈希起来
for(o=0,j=1;j^c[i];++j) {for(k=0;k^c[i]&&H[(o+k)%c[i]]==H[(j+k)%c[i]];++k);H[(j+k)%c[i]]<H[(o+k)%c[i]]&&(o=j);}//最小表示法
for(h=Hash(),j=o;j^c[i];++j) h=h*s3+H[j];for(j=0;j^o;++j) h=h*s3+H[j];S[ID(h)].push_back(i);//按最小表示哈希
}
RI tg=0;for(i=1;i<=cnt;++i) if(S[i].size()>1)//如果等价类大小超过1
{for(tg=1,it=S[i].begin();it!=S[i].end();++it) printf("%d ",*it);putchar('\n');}//输出等价类中元素
return !tg&&puts("none"),0;//如果没有超过1的等价类
}
待到再迷茫时回头望,所有脚印会发出光芒