题目链接:
UVA 10601 Cubes
题意:
给出12根等长的火柴棒,每根火柴棒的颜色属于
1−6
中的一种,问能拼成多少种不同的正方体?(考虑旋转变换)
分析:
首先正方体的旋转置换有
24
。下面将每个循环内元素的个数称为循环的长度。注意是棱边的置换循环,而不是面的置换循环
- 静止。只有一种置换。有 12 个循环,每个循环的长度为 1 。
- 以相对面的中心为轴旋转。可以旋转的角度是
90∘,180∘,270∘ 。选择轴有 3 种,每种轴下有3 种旋转,所以有 3∗3=9 种置换。
旋转 90∘,270∘ ,都是三个循环,每个循环的长度为 4 .
旋转180∘ ,有6个循环,每个循环的长度为2 - 以对边中点为轴,只可以旋转
180∘
,选择轴有6种,所以有6*1=6种置换。
在这种旋转下有5个长度为2的循环和2个长度为1的循环 - 以对顶点为为轴,可以旋转
120∘,240∘
。选择轴有4种,所以有4*2=8种置换。
旋转 120∘,240∘ 都是有4个长度为3的循环。
综上,可以验证置换数为
24
,可以找一个魔方,模拟旋转看看。找到了在每种置换下的循环个数和每个循环的长度就可以参考UVA 11255 Necklace中处理旋转置换的方法解决。
需要特别关注的是,
以对边中点为轴,只可以旋转180∘,有5个长度为2的循环和2个长度为1的循环。
我们可以先枚举两个长度为1的循环选择的颜色,然后对于5个长度为2的循环就和上面的一样了。
最后别忘了根据 Burnside引理 需要除以总的置换数 24 。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <climits>
#include <cmath>
#include <ctime>
#include <cassert>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
using namespace std;
typedef long long ll;
int T;
int a[10], b[10];
ll C[20][20];
void init()
{
C[0][0] = 1;
for(int i = 1; i < 15; ++i) {
C[i][0] = C[i][i] = 1;
for(int j = 1; j < i; ++j) {
C[i][j] = C[i - 1][j] + C[i - 1][j - 1];
}
}
}
ll work(int k)
{ //每k条边必须相同,分成12/k组(以对边中点为轴旋转180°是分成5组)
memcpy (b, a, sizeof(a));
int sum = 0;
for (int i = 1; i <= 6; ++i) {
if(b[i] % k) return 0;
b[i] /= k;
sum += b[i];
}
ll res = 1;
for (int i = 1; i <= 6; ++i) {
res *= C[sum][b[i]];
sum -= b[i];
}
return res;
}
ll solve()
{
ll res = 0;
//静止
res += work(1);
//以相对面中心为轴
res += (ll)3 * 2 * work(4); //旋转90°和270°
res += (ll)3 * work(2); //旋转180°
// 以对顶点为轴,可以旋转120°或240°
res += (ll)4 * 2 * work(3);
// 以对边种点为轴,只能旋转180°
for(int i = 1; i <= 6; ++i) {
for(int j = 1; j <= 6; ++j) {
if(a[i] == 0 || a[j] == 0) continue;
a[i]--; a[j]--; //将a[i]和a[j]设为选择的两条对边的颜色
res += (ll)6 * work(2); //剩下的是5个循环长度为2的循环,6代表对边选择情况
a[i]++; a[j]++;
}
}
return res;
}
int main()
{
init();
scanf("%d", &T);
while (T--) {
memset (a, 0, sizeof(a));
for (int i = 0; i < 12; ++i) {
int tmp;
scanf ("%d", &tmp);
a[tmp]++;
}
printf("%lld\n", solve() / 24); // 最后还要除以总的置换数:24
}
return 0;
}