评测地址:https://nanti.jisuanke.com/t/18515
之前几篇文章也说了,这道题,与金牌就差一点……
在赛后做了SGU 208,知道了polya定理的另外一种做法,然后在求证了 CSU Xushu Dalao 之后就知道了这题的具体做法。
昨天,无意中看到了计蒜客已经有重现的题目了,于是就按照之前所想的敲了敲。根据SGU 208的经验,我们需要对魔方的每一个小的面标号,然后手写出最基本的几个置换过程,然后搜索求出所有的置换。经过观察,我们可以发现,一个1*3*3的魔方的基础置换总共有三种,分别是整体旋转90°、某一条边旋转180°,和整体翻转180°。可能你会说,边的旋转不是还可以分成好几种吗?我可以上面的边转或者下面、左边右边的边转。但是实际上,我只选哟任意选取一条边的旋转作为基础置换即可。因为其他的边的旋转,我们可以通过先整体旋转90°再旋转的方式组合得到对应的置换。
根据polya定理,最后的答案,等于每种置换的循环数量对应的方案数的和,再除以总的置换数。于是在求出基础置换之后,我们就要搜索求所有的置换。这里我用到了bfs来搜置换,每次搜到置换之后用Hash判重,直到无法继续搜出新的置换。最后发现,总的置换数目为1536种。求出置换之后是每一个置换的循环数量。这些都是在预处理的时候完成。
此题是有n种颜色,没有其他限制,所以说如果一个置换有x个循环,那么其种类数就是n^x。由于给的模数不一定是个质数,所以我们最后在除以置换数的时候,不能按照往常一样求逆元。因此,只能用Java大整数去处理,在计算的时候不取模,最后除了之后再取模。但是要注意,这样的话不能一个置换一个置换的求,因为大整数运算较慢,很容易TLE。正确做法是,每个置换的循环数可以从1到30,故我们统计每个循环数对应的置换数,计算一次方再乘以对应循环数的置换数,节省计算的次数。然后Java的Map、Queue和String什么的都比较奇葩,第一次用,算是涨姿势了。具体见代码:
import java.math.*;
import java.util.*;
public class Main {
public static Map<String,Boolean> mp=new HashMap<String,Boolean>();
public static int nxt[]=new int[31],calc[]=new int[31],t=0;
public static BigInteger ans,m,n;
public static String turn_to(int nxt[]) //为了使用Map的方便,我把每种置换转化为String作为键值存
{ //这样省去了写结构体和重载运算符,况且Java好像不支持这个
String res=new String();
for(int i=1;i<=30;i++) //把置换转化为String
if (nxt[i]<10) res+="0"+nxt[i];
else res+=""+nxt[i];
return res;
}
public static void turn_back(String x) //把String转化回置换
{
for(int i=0;i<60;i+=2)
nxt[i/2+1]=(int)(x.charAt(i)-48)*10+(int)x.charAt(i+1)-48;
}
public static int cal() //计算置换的循环数
{
int res=0;
boolean v[]=new boolean[31];
for(int i=1;i<=30;i++)
if (!v[i])
{
for(int j=i;!v[j];j=nxt[j]) v[j]=true;
res++;
}
return res;
}
public static int[] Rotate() //整体向左旋转90°,好好想清楚,不要写错了
{
int nxe[]=new int[31];
System.arraycopy(nxt,1,nxe,1,30);
for(int i=7;i<=9;i++)
{
nxe[i]=nxt[i+15];
nxe[i+15]=nxt[i];
}
nxe[12]=nxt[27];nxe[27]=nxt[12];
nxe[28]=nxt[30];nxe[30]=nxt[28];
return nxe;
}
public static int[] Turn_Left() //某一条边旋转180°
{
int nxe[]=new int[31];
nxe[1]=nxt[3];nxe[2]=nxt[6];
nxe[3]=nxt[9];nxe[6]=nxt[8];
nxe[9]=nxt[7];nxe[8]=nxt[4];
nxe[7]=nxt[1];nxe[4]=nxt[2];
nxe[15]=nxt[12];nxe[10]=nxt[30];
nxe[11]=nxt[29];nxe[12]=nxt[28];
nxe[30]=nxt[27];nxe[29]=nxt[26];
nxe[28]=nxt[25];nxe[27]=nxt[13];
nxe[26]=nxt[14];nxe[25]=nxt[15];
nxe[13]=nxt[10];nxe[14]=nxt[11];
nxe[16]=nxt[22];nxe[19]=nxt[23];
nxe[22]=nxt[24];nxe[23]=nxt[21];
nxe[24]=nxt[18];nxe[21]=nxt[17];
nxe[18]=nxt[16];nxe[17]=nxt[19];
nxe[5]=nxt[5];nxe[20]=nxt[20];
return nxe;
}
public static int[] Reverse() //面整体翻转180°
{
int nxe[]=new int[31];
for(int i=1;i<=12;i++)
{
nxe[i]=nxt[i+15];
nxe[i+15]=nxt[i];
}
nxe[15]=nxt[13];nxe[13]=nxt[15];
nxe[28]=nxt[30];nxe[30]=nxt[28];
nxe[14]=nxt[14];nxe[29]=nxt[29];
return nxe;
}
public static void gettransformation() //广搜搜索所有的置换
{
Queue<String> q=new LinkedList<String>();
String s=turn_to(nxt); mp.put(s,true); q.offer(s);
while(!q.isEmpty())
{
String now=q.poll();
turn_back(now); t++; //统计置换数
s=turn_to(Rotate());
if (!mp.containsKey(s)) //判定该置换是否已经存在
{
mp.put(s,true); q.offer(s); //不存在则拓展
}
s=turn_to(Turn_Left());
if (!mp.containsKey(s))
{
mp.put(s,true); q.offer(s);
}
s=turn_to(Reverse());
if (!mp.containsKey(s))
{
mp.put(s,true); q.offer(s);
}
}
}
public static void main(String[] args)
{
Scanner cin=new Scanner(System.in);
int T_T=cin.nextInt(),tot=0;
for(int i=1;i<=30;i++)
nxt[i]=i;
gettransformation();
for(Map.Entry<String,Boolean> it:mp.entrySet()) //遍历所有的置换,并且求出循环数
{
turn_back(it.getKey());
calc[cal()]++;
}
for(int T=1;T<=T_T;T++)
{
ans=BigInteger.ZERO;
n=cin.nextBigInteger();
m=cin.nextBigInteger();
BigInteger p=BigInteger.ONE;
for(int i=1;i<=30;i++) //对于每一个循环数,计算其对应置换的答案
{
p=p.multiply(n);
ans=ans.add(p.multiply(BigInteger.valueOf(calc[i]))); //循环数为x的置换数*n^x
}
ans=ans.divide(BigInteger.valueOf(t)).mod(m); //除以总的置换数并且取模
System.out.println(ans);
}
}
}