2017 acm-icpc 青岛站 E题 FloppyCube (Polay计数 dfs找循环节)

题目连接:还没有地方挂出来

题意:给你一个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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值