计蒜客 Floppy Cube ACM/ICPC 2017 Qingdao(bfs+polya+Java大整数)

        评测地址: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);
		}
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值