HDU 6038(Function) 思维 Java

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();
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值