10-22 訓練 T2 plate
題目大意
有N個圓盤,每個圓盤的圓周上均勻分布了P個點(可連成正P邊形),編號\(P_1\)到 \(P_n\)。這P個點 中有M個關鍵點,所有關鍵點都是相同的。給出每個圓盤關鍵點位置的數據(對應的 \(P_i\)),現在 可以隨意轉動圓盤,問有多少對圓盤最終可以變成相同的形態。
思路
我們想要對兩個序列進行比較,由於編號不一樣,很明顯想到比較間距。但是間距數列的首項不固定,如果在所有數列的循環同構(把數列首尾接成環,所有的展開而成數列都是循環同構)中暴力比較,復雜度為 \(O(n^2)\) ,不太 \(OK\) 。這時就出現了一個神奇的算法:最小表示。
通過最小表示將序列按間距的字典序最小的方式排列,然后 \(O(n)\) 就可以比較了。
於是重點變成了如何以 \(O(n)\) 處理最小表示。
最小表示法
將數列 \(A\) 復制一份塞在其后來模擬環結構。
定義兩個指針 \(i\),\(j\) (初始為 \(1\) 和 \(2\))記錄兩個長度為 \(m\) 的數列的開頭,定義 \(k\) 為正在比較的位置距列首的距離。遍歷 \(k\),當比較發現 \(A_{i+k}\) 和 \(A_{j+k}\) 有差別時(這里不妨設是\(A_{i+k}< A_{j+k}\) ),說明\(A_{j},A_{j+1},A_{j+2},\dots,A_{j+k}\) 都不會是最小表示,那么我們將 \(j\) 跳到 \(j+k+1\) ,並且如果 \(j=i\) 時,我們將 \(j\) 再加一以保證比較的是兩個不一樣的排列。
如過 \(k\) 遍歷到了 \(m\),說明兩個排列完全相等,由於我們保證了不對比兩個一樣的排列,說明這時數列中的元素全部是一樣的。這時跳出函數以任意點做起點就行了。
如果 \(i\) 或 \(j\) 有一個超過了 \(m\) 那么所有的排列都被比較完了,那么仍在 \(m\) 范圍內的那個指針就作為數列的起點。
Code
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,p,ans;
int a[501][1001],dis[501][1001];
int st[501],num[501],fa[501];
int findd(int x){
if(x!=fa[x]) return fa[x]=findd(fa[x]);
else return fa[x];
}
void add(int x,int y){
int anx=findd(x),any=findd(y);
if(anx==any) return ;
ans+=num[x];
num[x]+=num[y];
num[y]=0;
fa[any]=anx;
}
int main()
{
scanf("%d %d %d",&n,&m,&p);
for(int i=1;i<=n;i++){
fa[i]=i;
num[i]=1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%d",&a[i][j]);
}
sort(a[i]+1,a[i]+1+m);
for(int j=2;j<=m;j++){
dis[i][j-1]=a[i][j]-a[i][j-1];
}
dis[i][m]=a[i][1]-a[i][m]+p;
for(int j=1;j<=m;j++){
dis[i][m+j]=dis[i][j];
}
int x=1,y=2,k=0;
while(x<=m && y<=m){
while(k<m && dis[i][x+k]==dis[i][y+k]) k++;
if(k==m) break;
if(dis[i][x+k] > dis[i][y+k]){
x=x+k+1;
k=0;
if(x==y) x++;
}
else{
y=y+k+1;
k=0;
if(x==y) y++;
}
}
st[i]=min(x,y);
}
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
int flag=1;
for(int l=0;l<m;l++){
if(dis[i][st[i]+l] != dis[j][st[j]+l]){
flag=0;
break;
}
}
if(flag){
add(i,j);
}
}
}
printf("%d",ans);
return 0;
}
最后統計答案做法很多,讀者可以考慮別的做法。
\(\beta y\quad\_thorn\)