2017 Multi-University Training Contest - Team 1 1006题
思维+循环节(递推关系)!!!
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.util.Arrays;
/**
* 题意:给你两个 数组,数组A是[0~n-1]的排列,数组B是[0~m-1]的排列。现在定义F(i)=B[F(A[i])];
* 问F(i)有多少种取值,使得表达式F(i)=B[F(A[i])]全部合法。
*
* 解题思路:思维和找循环节是这个题的关键所在。
* 由 A[] 生成的循环节个数,决定表达式F(i)=B[F(A[i])]有几种递推关系,每个循环节长度(不同的元素节点个数),决定每种递推关系需要取几个值。
* 由 B[] 生成的循环节个数,决定表达式F(i)=B[F(A[i])]有几种取值范围,每个循环节长度(不同的元素节点个数),决定每种取值范围的可取值个数。
*
* 如果表达式F(i)=B[F(A[i])]有 N 种不同的递推关系,一种递推关系需要取 x[i]个值,F[i]有 M 种取值范围,而每种取值范围里面的值的数量 y[j]个,
* 当且仅当:x[i] % y[j] == 0 时,才能使得表达式F(i)=B[F(A[i])]全部合法。
* F(i) 的取值:第 i 种递推关系下,满足上述条件的 y[j] 累加 得 sum[i]。最后求 sum[i]累乘 得结果。
*
* 先看样例:
* 如:A[]={1,0,2}、B[]={0,1} ==> F(i)的取值为:110、111、001、000 四种。
* 如:A[]={2,0,1}、B[]={0,2,3,1} ==> F(i)的取值为:000、231、312、123 四种
*
* Q:什么是循环节?
* A:这里的循环节可以理解成一种递推关系。
* 比如:A[1,0,2],那么 F(i)=B[F(A[i])] ==> F[0]=B[F(1)]、F[1]=B[F(0)]、F[2]=B[F(2)]
* 这里存在 两种 递推关系,即 F[0]->F[1]->F[0]、F[2]->F[2],其长度分别为 {2,1}
* 注意:千万不要理解为无向图,跟无向图是有区别的:
* 如:A[2,2,2,2] F[0]->F[2]->F[2],F[1]->F[2],F[3]-F[2] 是三种递推关系,其长度分别为 {2,1,1}
*
* Q:为什么要找循环节?
* A:因为在一条递推关系(循环节)中,任意一个元素的值被确定之后,循环节中其他的元素也就都确定了。通过循环节,我们可以解决这类的问题。
* 比如:在A[]={1,0,2} 的第一条循环节 F[0]->F[1]->F[0]中,当F[0]/F[1]的值被确定了,F[1]/F[0]的值也就被确定了
*
* Q:为什么要满足 x[i] % y[j] == 0 ?
* A:可以试试,如若不满足的话,不会满足表达式F(i)=B[F(A[i])]的所有关系。
*
* @author TinyDolphin
*
*/
public class Main {
private static int numOfLoopN; // 存放数组A[]中循环节的数量,即递推关系的条数
private static int numOfLoopM; // 存放数组B[]中循环节的数量,即取值范围的种数
private static int[] numN = new int[100010]; // 存放数组 A[]
private static int[] numM = new int[100010]; // 存放数组 B[]
private static int[] loopN = new int[100010]; // 存放数组 A[]中每个循环节的长度(每种递推关系需要取几个值)
private static int[] loopM = new int[100010]; // 存放数组 B[]中每个循环节的长度(每种取值范围的可取值个数)
private static boolean[] check = new boolean[100010];// 存放标记
/**
* 找出对应数组中循环节的个数以及每个循环节的长度
*
* @param input 数组中的元素个数
* @param num 存放数组
* @param loop 存放数组中每个循环节的长度
* @return 存放数组中循环节的数量
*/
private static int findLoop(int input,int[] num,int[] loop) {
int numOfLoop = 0;
for (int indexI = 0; indexI < input; indexI++) {
// 如果没有被标记
if (!check[indexI]) {
int indexJ = indexI;
int length = 0;
// 依次遍历每一条循环节(递推关系)
while (!check[indexJ]) {
length++; // 遍历一个元素节点 +1
check[indexJ] = true; // 标记数组中被遍历的数组下标
indexJ = num[indexJ]; // 查看下一个元素节点
}
loop[numOfLoop++] = length; // 记录每一条循环节的长度(不同的元素节点个数)
}
}
return numOfLoop;
}
public static void main(String[] args) throws IOException {
StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
int inputN;
int inputM;
int caseNum = 0;
while (in.nextToken() != StreamTokenizer.TT_EOF) {
inputN = (int) in.nval;
in.nextToken();
inputM = (int) in.nval;
// 初始化数组:全部置零
Arrays.fill(numN, 0);
Arrays.fill(numM, 0);
for (int index = 0; index < inputN; index++) {
in.nextToken();
numN[index] = (int) in.nval;
}
for (int index = 0; index < inputM; index++) {
in.nextToken();
numM[index] = (int) in.nval;
}
// 初始化
Arrays.fill(loopN, 0);
Arrays.fill(check, false);
// 找出递推关系循环节的个数以及每个循环节的长度(每种递推关系需要取几个值)
numOfLoopN = findLoop(inputN,numN,loopN);
// 初始化
Arrays.fill(loopM, 0);
Arrays.fill(check, false);
// 找出取值范围循环节的个数以及每个循环节的长度(决定每种取值范围有几个值可取)
numOfLoopM = findLoop(inputM,numM,loopM);
// 求满足条件的 F[i] 取值方案个数
int ans = 1;
// 遍历每一条递推关系
for (int indexI = 0; indexI < numOfLoopN; indexI++) {
int sum = 0; //记录第 i 条循环节(递推关系)下,一共有多少满足条件的可取值
// 遍历每一种取值范围下的可取值
for (int indexJ = 0; indexJ < numOfLoopM; indexJ++) {
// 满足条件:递推关系中需要的取值数 % 取值范围中的取值数 == 0
if (loopN[indexI] % loopM[indexJ] == 0) {
sum += loopM[indexJ]; // 在第 i 条递推关系下,每种取值范围的可取值个数之和
}
}
ans *= sum % 1000000007; // 每条递推关系中的 sum 累乘
}
out.print("Case #" + (++caseNum) + ": ");
out.println(ans);
}
out.flush();
}
}