1、置換
置換簡單來說就是對元素進行重排列,如下圖所示。置換是[1,n]到[1,n]的一一映射。
舉個直觀的例子,將正方形繞其中心逆時針旋轉90度,可以看成是正方形四個頂點的一個置換。關於置換、置換群的具體理論,請參考其他資料,此處有個大致印象就好。下面描述幾個結論。

(1)置換可以分解成若干循環,方法為:連邊1->a1,2->a2,…,i->ai,…,n->an,任取一個元素,順着有向邊走,直到回到出發點,即形成一個環,剩余元素如法炮制。
(2)如果一個狀態經過置換 f 后跟原來相同,即S[1] = S[a1], S[2] = S[a2], …, S[n] = S[an]。則稱該狀態為 f 的不動點。
(3)題目中常常出現“本質不同的方案數”,一般是指等價類的數目,題目定義一個等價關系,滿足等價關系的元素屬於同一等價類。等價關系通常是一個置換集合F,如果一個置換能把其中一個方案映射到另一個方案,則二者是等價的。
2、burnside引理
對於一個置換f,若一個染色方案s經過置換后不變,稱s為f的不動點。將f的不動點數目記為C(f),則可以證明等價類數目為所有C(f)的平均值。
如上圖(圖片來自百度百科“burnside引理”)所示,對於四個置換{逆時針旋轉0°,逆時針旋轉90°,逆時針旋轉180°,逆時針旋轉270°},其不動點數分別為16, 2, 4, 2。所以等價類數目為(16+2+4+2)/4 = 6。
3、polya定理
polay定理實際上是burnside引理的具體化,提供了計算不動點的具體方法。
假設一個置換有k個循環,易知每個循環對應的所有位置顏色需一致,而任意兩個循環之間選什么顏色互不影響。因此,如果有m種可選顏色,則該置換對應的不動點個數為m^k。用其替換burnside引理中的C(f),得到等價類數目為:

其中|F|表示置換的數目,ki表示第i個置換包含的循環個數。
4、例題
// LA_3641 Leonardo's Notebook #include <cstdio> using namespace std; int main() { int T; char B[30]; int c[30]; bool v[30]; scanf("%d", &T); while(T--) { scanf("%s", B); for(int i = 0; i <= 26; i++) c[i] = 0, v[i] = 0; for(int i = 0; i < 26; i++){ if(v[i] == 0) { v[i] = 1; int t = 1, j = B[i]-'A'; for(; j != i; j = B[j]-'A') t++, v[j] = 1; c[t]++; } } bool flag = true; for(int i = 2; i <= 26; i += 2) if(c[i]&1) flag = false; puts(flag ? "Yes" : "No"); } return 0; }
// LA_3510 Pixel Shuffle #include <cstdio> #include <cctype> #include <cstring> using namespace std; char oper[35][10]; const int maxn = 1024; int ori[maxn*maxn]; #define ID(i, j) ((i)*n+(j))//注意這樣定義函數的時候,一定不要偷懶,省略任意一個括號,都可能產生致命的后果 int NewPos(int i, int j, int n, char *op) { if(op[0] == 'i') return ID(i, j); if(op[0] == 'r') return ID(n-1-j, i); if(op[0] == 's') return ID(i, n-1-j); if(op[0] == 'b' && op[1] == 'h') return (2*i >= n) ? ID(i, n-1-j) : ID(i, j); if(op[0] == 'b' && op[1] == 'v') return (2*i >= n) ? ID(n/2+n-i-1, j) : ID(i, j); if(op[0] == 'd') return (i&1) ? ID(n/2+i/2, j) : ID(i/2, j); if(op[0] == 'm') { int k = (i>>1)<<1; if(j < n/2) return ID(k, (j<<1)+(k!=i)); else { j -= n/2; return ID(k+1, (j<<1)+(k!=i)); } } } void apply(int* image, int n, char *op)//維護一個當前的序列 { bool div = 0; if(op[strlen(op)-1] == '-') div = 1; for(int i = 0; i < n*n; i++) ori[i] = image[i]; int mx = -1, mi = -1; for(int i = 0; i < n; i++) { for(int j = 0; j < n; j++){ int p = ID(i, j), p2 = NewPos(i, j, n, op); if(div) image[p] = ori[p2]; else image[p2] = ori[p]; } } } bool v[maxn*maxn]; int gcd(int x, int y) { return y == 0 ? x : gcd(y, x%y);} int lcm(int x, int y) { return x/gcd(x, y)*y; } int solve(int* image, int n)//對於給定的置換,計算其各個循環長度的最小公倍數 { int ans = 1; for(int i = 0; i < n; i++) v[i] = 0; for(int i = 0; i < n; i++){ if(!v[i]){ v[i] = 1; int c = 1, j = image[i]; for( ; j != i; j = image[j]) c++, v[j] = 1; ans = lcm(ans, c); } } return ans; } int cur[maxn*maxn]; int main() { int T, n; scanf("%d %d", &T, &n); while(T--) { int c = 0, n1; while(~scanf("%s", oper[c])) { if(isdigit(oper[c][0])){ sscanf(oper[c], "%d", &n1); break; } c++; } for(int i = 0; i < n*n; i++) cur[i] = i; for(int i = c-1; i >= 0; i--) apply(cur, n, oper[i]); printf("%d\n", solve(cur, n*n)); if(T) puts(""); n = n1; } return 0; }
