题目连接:还没有地方挂出来
题意:给你一个3*3*1的魔方,然后给你n个颜色,问有多少种本质不同的魔方。本质相同的魔方是指通过旋转翻转,或者向魔方那样转动一条边,可以变成同一个魔方。
思路:很明显这是一个polay计数。polay的关键就是求出所有的置换,和所有置换的循环节个数。但是这个置换实在是太多,而且也不好枚举,现场竟然在手动枚举,现在想想真是zz。其实这一题可以用一个比较优美的姿势用电脑来暴力枚举。
首先我们先定义一个魔方和上下面还有编号。
3*3的面放在水平面。然后6个面分别记为(上面,下面,前面,后面,左面,右面),然后上面从左往右,从上往下记为1到9,下面(从上往下看)从左往右,从上往下记为10到18,前面从左往右19到21,后面(从后往前看)从左往右22到24,左面(从左往右看)从左往右25到27,右面(从右往左看)从左往右看28到30。
首先我们可以观察,发现所有置换可以通过三种基本操作来完成,1)转动右边一条棱(这里叫做魔方转动),2)沿着3*3面的中垂线,顺时针旋转90°,3)沿着456这条线翻转180°。所有其他操作可以通过这三种基本操作实现,可以自己想想。
我们将同过这三种基础的操作衍生出所有的置换。将我上述的最初状态当做一个不变的置换,放入一个set中,然后通过这三种操作,不断衍生新的置换,直到不能衍生为止。
所有的置换求出来了,一共1536种。然后就是求出每个一个置换的循环节个数。这个很简单,每个置换只有30个元素,直接暴力找一下就好了。为了最后方便统计。我们可以将这些置换按照循环节个数进行分类。这样在最后算的会方便一些。
最后一个统计的时候还有一个啃,就是这个模数不是一个质数,不是质数的时候就不能通过求逆元来替换除法了。现场听说好多人过的人是用java大数碾过去的,其实我们可以把 a/b mod p 转换成 (a mod bp)/b,这个在a整除b的时候满足。
证明:
a=bk
k=p*x+m
a = b*p*x+m*b
所以a mod bp以后,肯定能被b整除
这样我们就有了这道题的完整解法,但是注意到这里还有一个坑,这里在取模的时候发现模数由于相乘了,范围就有1e11所以在进行乘法的时候还要爆longlong,所以还得写一个快速乘法。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <iostream>
#include <vector>
#include <map>
#include <queue>
#include <stack>
#include <set>
using namespace std;
#define pb push_back
#define pi acos(-1)
typedef long long ll;
typedef unsigned long long ull;
const long long mod = 1000000007;
#define maxn 111111
#define maxm 11111
//三个基础置换
//右边一条棱拧一下
int a1[31] = {0, 1, 2, 18, 4, 5, 15, 7, 8, 12, 10,
11, 9, 13, 14, 6, 16, 17, 3, 19, 20,
22, 21, 23, 24, 25, 26, 27, 30, 29, 28};
//绕着上面中心旋转90°顺时针转动
int a2[31] = {0, 7, 4, 1, 8, 5, 2, 9, 6, 3, 16,
13, 10, 17, 14, 11, 18, 15, 12, 28, 29,
30, 25, 26, 27, 19, 20, 21, 22, 23, 24};
//前后翻转
int a3[31] = {0, 16, 17, 18, 13, 14, 15, 10, 11, 12, 7,
8, 9, 4, 5, 6, 1, 2, 3, 24, 23,
22, 21, 20, 19, 27, 26, 25, 30, 29, 28};
vector <int> qun[4]; //将三种置换群放到vector中,方便后面的运算。
set<vector<int> > zh;
set<vector<int> > ::iterator it;
vector<int> mul(vector<int> a, vector<int> b) {
vector<int> ans;
for(int i = 0; i <= 30; i++) ans.pb(a[i]);
for(int i = 0; i <= 30; i++) ans[i] = b[ans[i]];
return ans;
}
//dfs出所有的置换
void dfs(vector<int> x) {
for(int i = 1; i <= 3; i++) {
vector<int> temp = mul(x, qun[i]);
if(zh.count(temp) == 0) {
//cout << xx << endl;
zh.insert(temp);
dfs(temp);
}
}
}
map<int, int> mp;
map<int, int> ::iterator it2;
int vis[31];
//找出所有置换的循环节
void findcycle() {
for(it = zh.begin(); it != zh.end(); it++) {
vector<int> temp = *it;
memset(vis, 0, sizeof vis);
int cnt = 0;
for(int i = 1; i <= 30; i++) {
if(vis[i] == 0) {
cnt++;
int tt = i;
vis[i] = 1;
while(!vis[temp[tt]]) {
tt = temp[tt];
vis[tt] = 1;
}
}
}
mp[cnt]++;
}
}
void init() {
for(int i = 1; i <= 3; i++) qun[i].clear();
for(int i = 0; i <= 30; i++) qun[1].pb(a1[i]);
for(int i = 0; i <= 30; i++) qun[2].pb(a2[i]);
for(int i = 0; i <= 30; i++) qun[3].pb(a3[i]);
vector<int> temp;
for(int i = 0; i <= 30; i++) temp.pb(i);
zh.insert(temp);
dfs(temp);
findcycle();
//cout << zh.size() << endl;
}
//快速乘法
long long qmut(long long a, long long b, long long p) {
long long ans = 0;
long long temp = a;
while(b) {
if(b & 1LL) {
ans = (ans + temp) % p;
}
temp = (temp + temp) % p;
b /= 2LL;
}
return ans;
}
//快速幂
long long qpow(long long x, long long n, long long p) {
long long ans = 1;
long long temp = x;
while(n) {
if(n & 1LL) {
ans = qmut(ans, temp, p);
}
temp = qmut(temp, temp, p);
n /= 2LL;
}
return ans;
}
long long n, p;
int main()
{
init();
int t;
scanf("%d", &t);
long long sz = zh.size();
while(t--) {
scanf("%lld%lld", &n, &p);
long long ans = 0;
for(it2 = mp.begin(); it2 != mp.end(); it2++) {
ans = (ans + (*it2).second * qpow(n, (*it2).first, p * sz) % (p * sz)) % (p * sz);
//用刚才的姿势进行取模
}
ans /= sz;
printf("%lld\n", ans);
}
return 0;
}