• BestCoder 百度之星2016


    20160523 百度之星初赛第一场

    1001 All X

     
    Problem Description

    F(x, m)F(x,m) 代表一个全是由数字xx组成的mm位数字。请计算,以下式子是否成立:

    F(x,m) mod k  c

    Input

    第一行一个整数TT,表示TT组数据。 每组测试数据占一行,包含四个数字x,m,k,c

    1x9

    1m10​^10

    0c<k10,000

    Output

    对于每组数据,输出两行: 第一行输出:"Case #i:"。ii代表第ii组测试数据。 第二行输出“Yes” 或者 “No”,代表四个数字,是否能够满足题目中给的公式。

    Sample Input
    3
    1 3 5 2
    1 3 5 1
    3 5 99 69
    
    Sample Output
    Case #1:
    No
    Case #2:
    Yes
    Case #3:
    Yes
    Hint
    对于第一组测试数据:111 mod 5 = 1,公式不成立,所以答案是”No”,而第二组测试数据中满足如上公式,所以答案是 “Yes”。

    思路 :对于m个x组成的数,这个数太大无法直接计算。所以应该从另外的角度考虑问题。

      首先我们得明确对于乘法和加法的求模运算,可以先乘(加)后模或者先模后乘(加),所得结果是一样的。比如(a*b)%c == ((a%c) * (b%c))%c; (a+b)%c =(a%c+b%c)%c.

      所以这一题我们可以转化到求m个1组成的数对k的余数,同样的这个数也很大,但是k的范围不大,我们知道余数的个数最多是k个不同的值,所以余数是有周期的或者说从某一个数开始会循环。我们需要找到周期开始和周期结束的位置。假设 a个1对k的余数是 ak,1的个数不断增加,直到b个1对k的余数 bk == ak;就会得出周期开始是a个1,周期结束是b-1个1.此后,无论1的个数增加到多少,都是在a~b-1这个区间内,区间长度为 b-a。

      当有m个数时,m < b时,余数即第m个;m>b时,余数在(m-b)%(b-a)+a 这个位置。

    import java.util.HashMap;
    import java.util.Map;
    import java.util.Scanner;
    
    public class Main{
         public static void main(String[] args) {
                Scanner in = new Scanner(System.in);
                while (in.hasNext()) {
                    int t = in.nextInt();
                    for (int i = 1; i <= t; i++) {
                        int x = in.nextInt();
                        long m = in.nextLong();
                        int k = in.nextInt();
                        int c = in.nextInt();
                        
                        Map<Integer,Integer> map = new HashMap<Integer,Integer>();
                        int[] ref = new int[k + 1];
                        int allOne = 1;
                        int index = 1;
                        //计算1~index个1对k的余数,余数个数最多为k个,所以肯定从某个位置开始有周期,找出周期
                        //某个余数第二次出现了,这就是周期开始位置
                        while (!map.containsKey(allOne)) {
                            map.put(allOne, index);
                            ref[index++] = allOne;
                            allOne = (allOne * 10 + 1) % k;
                        }
                        int start = map.get(allOne);//周期开始的位置,map的最后一项是周期结束的位置
                        int period = index - start;//周期长度
                        System.out.println("Case #" + i + ":");
                        int res = 0;
                        if (m < index) {
                            res = (ref[(int) m] * x) % k; //转化成m个x的余数
                        } else {
                            res = (ref[(int)((m - index) % period + start)] * x) % k;
                        }
                        System.out.println(res == c ? "Yes" : "No");
                    }
                }
         }
    }

    20160514 百度之星资格赛

    Problem A

     

    度熊手上有一本字典存储了大量的单词,有一次,他把所有单词组成了一个很长很长的字符串。现在麻烦来了,他忘记了原来的字符串都是什么,神奇的是他竟然记得原来那些字符串的哈希值。一个字符串的哈希值,由以下公式计算得到:

    H(s)=prod_{i=1}^{ileq len(s)}(S_{i}-28) (mod 9973)H(s)=i=1ilen(s)​​(Si​​28) (mod 9973)

    S_{i}Si​​代表 S[i] 字符的 ASCII 码。

    请帮助度熊计算大字符串中任意一段的哈希值是多少。

    Input

    多组测试数据,每组测试数据第一行是一个正整数NN,代表询问的次数,第二行一个字符串,代表题目中的大字符串,接下来NN行,每行包含两个正整数aa和bb,代表询问的起始位置以及终止位置。

    1leq Nleq 1,0001N1,000

    1leq len(string)leq 100,0001len(string)100,000

    1leq a,bleq len(string)1a,blen(string)

    Output

    对于每一个询问,输出一个整数值,代表大字符串从 aa 位到 bb 位的子串的哈希值。

    Sample Input
    2
    ACMlove2015
    1 11
    8 10
    1
    testMessage
    1 1
    
    Sample Output
    6891
    9240
    88



    思路:本题要用到乘法逆元+ 费马小定理+快速模取幂算法,这三个之前都没有接触过,这里总结一下
    1.乘法逆元
      满足 a*k≡1 (mod p)的k值就是a关于p的乘法逆元。也就是a*k mod p = 1。
    什么时候要用到乘法逆元呢?
    当我们要求(a/b) mod p的值,且a很大,无法直接求得a/b的值时,我们就要用到乘法逆元。
    我们可以通过求b关于p的乘法逆元k,将a乘上k再模p,即(a*k) mod p。其结果与(a/b) mod p等价。

    证:(其实很简单。。。)
    根据b*k≡1 (mod p)有b*k=p*x+1。
    k=(p*x+1)/b。
    把k代入(a*k) mod p,得:
    (a*(p*x+1)/b) mod p
    =((a*p*x)/b+a/b) mod p
    =[((a*p*x)/b) mod p +(a/b)] mod p
    =[(p*(a*x)/b) mod p +(a/b)] mod p
    //p*[(a*x)/b] mod p=0
    所以原式等于:(a/b) mod p
    这一题中就是要求(a/b) mod p (p = 9973),那么怎么去求这个乘法逆元呢,这里就要用到费马小定理。
    2.费马小定理
    --百度百科(http://baike.baidu.com/link?url=L9XOakOsc-s9IK8cnRHJCh060JnJYbIDhkNpVqnIynK1b8_lC3pDALOoSiUa4CRsz9ZXHp2xKp_tVD9IEn9BSa)
    费马小定理(Fermat Theory)是数论中的一个重要定理,其内容为: 假如p是质数,且(a,p)=1,那么 a(p-1)≡1(mod p)。
    即:假如a是整数,p是质数,且a,p互质(即两者只有一个公约数1),那么a的(p-1)次方除以p的余数恒等于1。
    这里p = 9973,所以a的乘法逆元也就是 a^(p-2),所以我们要求这个(同时也要mod 9973)。

    3.要求 a^b % c的时候,当然可以直接循环然后每一步都mod c,这样的复杂度就是O(b)了,但是快速模取幂算法可以降低复杂度。

    描述如下:

      可以把b按二进制展开为:b = p(n)*2^n  +  p(n-1)*2^(n-1)  +…+   p(1)*2  +  p(0)

      其中p(i) (0<=i<=n)为 0 或 1

      样 a^b =  a^ (p(n)*2^n  +  p(n-1)*2^(n-1)  +...+  p(1)*2  +  p(0))
                   =  a^(p(n)*2^n)  *  a^(p(n-1)*2^(n-1))  *...*  a^(p(1)*2)  *  a^p(0)
      对于p(i)=0的情况, a^(p(i) * 2^(i-1) ) =  a^0  =  1,不用处理
      我们要考虑的仅仅是p(i)=1的情况
      化简:a^(2^i)  = a^(2^(i-1)  * 2) = (  a^(  p(i)  *  2^(i-1)  )  )^2
      (这里很重要!!具体请参阅秦九韶算法:http://baike.baidu.com/view/1431260.htm
      利用这一点,我们可以递推地算出所有的a^(2^i)
      当然由算法1的结论,我们加上取模运算:
      a^(2^i)%c = ( (a^(2^(i-1))%c) * a^(2^(i-1)))  %c

     

    相应的算法将在这道题的AC代码中写出。
    import java.util.Scanner;
    
    public class Main {
        
        static int[] H =  new int[100001];
        static int mods = 9973;
        
        public static void main(String[] args) {
            Scanner sc = new Scanner(System.in);
            while(sc.hasNext()){
                int N = sc.nextInt();
                String str = sc.next();
                
                getHashFactor(str);
                for (int i = 0; i < N; i++) {
                    int begin = sc.nextInt() -1;
                    int end = sc.nextInt() ;
                    System.out.println(getHashCode(begin,end));
                }
            }
        } 
        //保存从开始到当前位置前面字符串的hash值
        private static void getHashFactor(String ss) {
            int len = ss.length();
            H[0] = 1;
            for(int i = 1;i <= len;i ++){
                H[i] = H[i - 1] * (ss.charAt(i-1) - 28) % mods;
            }
        }
        //利用费马小定理和乘法逆元
        private static int getHashCode(int begin,int end) {
            return (int) (H[end]*mod_pow(H[begin], mods-2, mods)%mods);
        }
        //快速模取幂算法
        private static long mod_pow(long x, long n, long mod) {
            long res = 1;
            while(n > 0) {
                if((n & 1) != 0) res = res * x % mod;
                x = x * x % mod;
                n >>= 1;
            }
            return res;
        }
    }
     

    Problem D

    度熊所居住的 D 国,是一个完全尊重人权的国度。以至于这个国家的所有人命名自己的名字都非常奇怪。一个人的名字由若干个字符组成,同样的,这些字符的全排列的结果中的每一个字符串,也都是这个人的名字。例如,如果一个人名字是 ACM,那么 AMC, CAM, MAC, MCA, 等也都是这个人的名字。在这个国家中,没有两个名字相同的人。

    度熊想统计这个国家的人口数量,请帮助度熊设计一个程序,用来统计每一个人在之前被统计过多少次。

    Input

    这里包括一组测试数据,第一行包含一个正整数NN,接下来的NN 行代表了 NN 个名字。NN 不会超过100,000100,000,他们的名字不会超过40位.

    Output

    对于每输入的一个人名,输出一个整数,代表这个人之前被统计了多少次。

    Sample Input
    5
    ACM
    MAC
    BBA
    ACM
    BAB
    
    Sample Output
    0
    1
    0
    2
    1


    思路:关键要想到java里面Arrays.sort()函数可以将字符数组按照ascii码排序。排序之后,同一个名字所组成的字符串就相同了,
    再利用HashMap,键记录名字,值记录当前已出现的次数,每遇到一次就把值加一,更新map里的值部分。

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.Map;
    
    public class Main {
        public static void main(String args[]) throws IOException{
            InputStreamReader is = new InputStreamReader(System.in);
            BufferedReader br = new BufferedReader(is);
            String str = "";
            while ((str = br.readLine()) != null) {
                int n = Integer.valueOf(str);
                Map<String,Integer> m = new HashMap<String,Integer>();            
                for (int i = 0; i < n; i++) {
                    char[] sa = br.readLine().toCharArray();
                    Arrays.sort(sa);
                    String sorted = new String(sa); 
                    if (m.containsKey(sorted)) {
                        m.put(sorted, m.get(sorted)+1);
                        System.out.println(m.get(sorted));
                    } else {
                        m.put(sorted, 0);
                        System.out.println(0);
                    }
                }
            }
        }
    }
    
    
    需要注意的是 Map里面存储的是对象,所以在将排序好的字符数组put进map的时候,需要 新建String对象,而不能直接put,要不然无法判断是否相等,
    在利用equals函数判断时,也是如此。看下面的代码即可。
    public class Main {
        public static void main(String args[]) throws IOException{
            String a = "ss";
            String b = "ss";
            char[] arr = {'s','s'};
            String c = arr.toString();
            String d = new String(arr);
            if (a.equals(b))
                System.out.println("a == b");
            else 
                System.out.println("a != b");
            if (a.equals(c))
                System.out.println("a == c");
            else
                System.out.println("a != c");
            if (a.equals(d))
                System.out.println("a == d");
            else 
                System.out.println("a != d");
            if (c.equals(d))
                System.out.println("c == d");
            else
                System.out.println("c != d");
            if ("ss".equals("ss"))
                System.out.println("ss == ss");
            else
                System.out.println("ss != ss");        
        }
    }
    
    
    打印结果是:

    a == b
    a != c
    a == d
    c != d
    ss == ss

    Problem B

     

    度熊面前有一个全是由1构成的字符串,被称为全1序列。你可以合并任意相邻的两个1,从而形成一个新的序列。对于给定的一个全1序列,请计算根据以上方法,可以构成多少种不同的序列。

    Input

    这里包括多组测试数据,每组测试数据包含一个正整数NN,代表全1序列的长度。

    1leq N leq 2001N200

    Output

    对于每组测试数据,输出一个整数,代表由题目中所给定的全1序列所能形成的新序列的数量。

    Sample Input
    1
    3
    5
    
    Sample Output
    1
    3
    8
    Hint
    如果序列是:(111)。可以构造出如下三个新序列:(111), (21), (12)。

    思路:找规律发现就是求斐波那契数列的值。陷阱在于如果使用long类型来保存结果会越界,所以要用BigInteger,还要注意保存之前的结果,就像测试赛的第一题。

    import java.math.BigInteger;
    import java.util.Scanner;
    
    public class Main {
        public static void main(String[] args) {
            Scanner sc = new Scanner(System.in);
            int max = 1;
            System.out.println(Long.MAX_VALUE);
            BigInteger[] bi = new BigInteger[201];
            bi[0] = new BigInteger("1");
            bi[1] = new BigInteger("1");
            while (sc.hasNext()) {
                int n = sc.nextInt();
                while (n > max) {
                    bi[max+1] = bi[max].add(bi[max-1]);
                    max++;
                }
                System.out.println(bi[n]);
            }
        }
    }

    2016 0510 百度之星测试赛

    1004 放盘子

    小度熊喜欢恶作剧。今天他向来访者们提出一个恶俗的游戏。他和来访者们轮流往一个正多边形内放盘子。最后放盘子的是获胜者,会赢得失败者的一个吻。玩了两次以后,小度熊发现来访者们都知道游戏的必胜策略。现在小度熊永远是先手,他想知道他是否能获胜。

    注意盘子不能相交也不能和多边形相交也不能放在多边形外。就是说,盘子内的点不能在多边形外或者别的盘子内。

    Input

    第一行一个整数TT,表示TT组数据。每组数据包含33个数n,a,r (4 leq n leq 100,0 < a < 1000,0 < r < 1000)n,a,r(4n100,0<a<1000,0<r<1000)

    nn是偶数,代表多边形的边数,aa代表正多边形的边长,rr代表盘子的半径。

    Output

    对于每组数据,先输出一行

    Case #i:

    然后输出结果.如果小度熊获胜,输出”Give me a kiss!” 否则输出”I want to kiss you!”

    Sample Input
    2
    4 50 2.5
    4 5.5 3
    
    Sample Output
    Case #1:
    Give me a kiss!
    Case #2:
    I want to kiss you!
    Hint
    在第一组样例中,小度熊先在多边形中间放一个盘子,接下来无论来访者怎么放,小度熊都根据多边形中心与来访者的盘子对称着放就能获胜。

    思路:不得不说最后的Hint至关重要,差点就蒙了。想通了之后非常简单,就是判断所给圆半径是否不大于正多边形的内切圆半径,不大于的话就会赢,反之则会输。

      因为不大于的时候,先手总是可以把盘子放在中心,然后在对称位置放置盘子就好了。反之,则一个盘子也放不进去,所以就会输。

    import java.util.Scanner;
    
    public class Main {
        public static void main(String []args){
            Scanner sc = new Scanner(System.in);
            while (sc.hasNext()) {
                int t = sc.nextInt();
                for (int i = 1; i <= t; i++) {
                    int n = sc.nextInt();
                    double a = sc.nextDouble();
                    double r = sc.nextDouble();
                    double ra = a/2 * 1/Math.tan(Math.PI/n);
                    System.out.println("Case #"+i+":");
                    if(ra >= r)
                        System.out.println("Give me a kiss!");
                    else
                        System.out.println("I want to kiss you!");
                }
            }
        }
    }

    1003 IP聚合

    当今世界,网络已经无处不在了,小度熊由于犯了错误,当上了度度公司的网络管理员,他手上有大量的 IP列表,小度熊想知道在某个固定的子网掩码下,有多少个网络地址。网络地址等于子网掩码与 IP 地址按位进行与运算后的结果,例如:

    子网掩码:A.B.C.D

    IP 地址:a.b.c.d

    网络地址:(A&a).(B&b).(C&c).(D&d)

    Input

    第一行包含一个整数TT,(1 leq T leq 50)(1T50)代表测试数据的组数,

    接下来TT组测试数据。每组测试数据包含若干行,

    第一行两个正整数N(1 leq N leq 1000, 1 leq M leq 50),MN(1N1000,1M50),M。接下来NN行,每行一个字符串,代表一个 IP 地址,

    再接下来MM行,每行一个字符串代表子网掩码。IP 地址和子网掩码均采用 A.B.C.DA.B.C.D的形式,其中A,B,C,DA,B,C,D均为非负整数,且小于等于255。

    Output

    对于每组测试数据,输出两行:

    第一行输出: "Case #i:" 。ii代表第ii组测试数据。

    第二行输出测试数据的结果,对于每组数据中的每一个子网掩码,输出在此子网掩码下的网络地址的数量。

    Sample Input
    2
    5 2
    192.168.1.0
    192.168.1.101
    192.168.2.5
    192.168.2.7
    202.14.27.235
    255.255.255.0
    255.255.0.0
    4 2
    127.127.0.1
    10.134.52.0
    127.0.10.1
    10.134.0.2
    235.235.0.0
    1.57.16.0
    Sample Output
    Case #1:
    3
    2
    Case #2:
    3
    4

    其实是一个很简单的题,但是容易掉进思维陷阱(当然可能是我太笨了)。一开始想的是将ip分段与掩码按位与,再分段比较。
        再将前一段相同的ip再去比较下一段,思考了半天,这样太复杂,估计会超时,实现起来也有难度。
    正确的思路:将每一个ip和子网掩码组合成int类型,int类型也是32位,ip和子网掩码都是32位,所以通过移位操作刚好能组合成int,
    再将组合之后的int类型的ip与子网掩码按位与,结果放入set里面,set的大小即结果了。
    import java.io.IOException;
    import java.util.HashSet;
    import java.util.Scanner;
    import java.util.Set;
    
    public class Main {
        public static void main(String []args) throws IOException{
            Scanner sc = new Scanner(System.in);
            while (sc.hasNext()) {
                int t = sc.nextInt();
                for (int i = 1; i <= t; i++) {
                    int n = sc.nextInt();
                    int m = sc.nextInt();
                    sc.nextLine();
                    String[] ips = new String[4];
                    String[] masks = new String[4];
                    int[] ipToInt = new int[n];
                    for (int j = 0; j < n; j++) {
                        ips = sc.nextLine().split("\.");
                        //这种写法比较好
                        for (String ip : ips) {
                            ipToInt[j] = (ipToInt[j]<<8) + Integer.valueOf(ip);
                        }
                    }
                    System.out.println("Case #"+i+":");
                    int[] ans = new int[m]; 
                    for (int j = 0; j < m; j++) {
                        masks = sc.nextLine().split("\.");
                        //这种写法必须加括号
                        int maskToInt =  (Integer.valueOf(masks[0])<<24) + 
                                (Integer.valueOf(masks[1])<<16) +
                                (Integer.valueOf(masks[2])<<8) +
                                (Integer.valueOf(masks[3]));
                        Set<Integer> set = new HashSet<Integer>();
                        for (int k = 0; k < n; k++) {
                            set.add(ipToInt[k] & maskToInt);
                        }
                        ans[j] = set.size();                
                    }
                    for (int j = 0; j < m; j++) {
                        System.out.println(ans[j]);
                    }
                }
            }
        }    
    }
    
    

    代码中需要注意的就是  1.ip组合成int时的写法,代码中有两种方式,第二种方式必须加括号,要不然结果不正确。

                2.split函数分割点时,记得加上转义符“\”。

     


    1002 列变位法解密

     

    列变位法是古典密码算法中变位加密的一种方法,具体过程如下 将明文字符分割成个数固定的分组(如5个一组,5即为密钥),按一组一行的次序整齐排列,最后不足一组不放置任何字符,完成后按列读取即成密文。

    比如:

    原文:123456789

    密钥:4

    变换后的矩阵:

    1234

    5678

    9xxx

    (最后的几个x表示无任何字符,不是空格,不是制表符,就没有任何字符,下同)

    密文:159263748

    再比如:

    原文:Hello, welcome to my dream world!

    密钥:7

    变换后的矩阵:

    Hello,

    welcome

    to my

    dream w

    orld!xx

    密文:

    Hw doeetrrlloellc adoomm!,my e w

    实现一个利用列变位法的加密器对Bob来说轻而易举,可是,对Bob来说,想清楚如何写一个相应的解密器似乎有点困难,你能帮帮他吗?

    Input

    第一行一个整数TT,表示TT组数据。

    每组数据包含22行

    第一行,一个字符串s(1 leq |s| leq 1e5)s(1s1e5),表示经过列变位法加密后的密文

    第二行,一个整数K(1 leq K leq |s|)K(1Ks),表示原文在使用列变位法加密时的密钥

    输入保证密文字符串中只含有ASCII码在[0x20,0x7F)[0x20,0x7F)范围内的字符

    Output

    对于每组数据,先输出一行

    Case #i:

    然后输出一行,包含一个字符串s_decrypt,表示解密后得到的明文

    Sample Input
    4
    159263748
    4
    Hw doeetrrlloellc adoomm!,my  e w
    7
    Toodming is best
    16
    sokaisan
    1
    
    Sample Output
    Case #1:
    123456789
    Case #2:
    Hello, welcome to my dream world!
    Case #3:
    Toodming is best
    Case #4:
    sokaisan
    思路:属于一看题就知道大概的思路的,但是bug-free对于我来说写起来还有难度。调试了好多遍才成功,边界情况处理的不是很好,暂时不想再研究了,好歹也是过了。
    值得注意的地方是StringBuilder的使用,如果是直接新建一个String的话,会超时。
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    
    public class Main {
        public static void main(String[] args) throws IOException {
            InputStreamReader isr = new InputStreamReader(System.in);
            BufferedReader br = new BufferedReader(isr);
            String str = "";
            while ((str = br.readLine()) != null) {
                int t = Integer.parseInt(str);
                for (int i = 1; i <= t; i++) {
                    String s = br.readLine();
                    int k = Integer.parseInt(br.readLine());
                    if (k >= s.length() || k == 1) {
                        System.out.println("Case #"+i+":");
                        System.out.println(s);
                    } else {
                        int mod = s.length() % k;
                        int row = s.length() / k;
                        if (mod != 0) {
                            row++;                        
                        } else {
                            mod = k;
                        }                        
                        int j = 0,p = 0,l = 0;
                        StringBuilder sb = new StringBuilder();
                        while ( p < row) {
                            l = 0;
                            while (l <= mod) {
                                sb.append(s.charAt(l*row+p));
                                j = l*row+p;
                                l++;
                                if (l == mod && (p == row-1 || mod == k))
                                    break;                            
                            }
                            if (sb.length() == s.length())
                                break;
                            while ((j+row-1 < s.length()) && (mod != k)) {
                                sb.append(s.charAt(j+row-1));
                                j = j+row-1;
                            }
                            p++;
                        }
                        System.out.println("Case #"+i+":");
                        System.out.println(sb.toString());
                    }                
                }
            }
        }    
    }
    
    
    


    1001 大搬家

     
    Problem Description

    近期B厂组织了一次大搬家,所有人都要按照指示换到指定的座位上。指示的内容是坐在位置ii上的人要搬到位置jj上。现在B厂有NN个人,一对一到NN个位置上。搬家之后也是一一对应的,改变的只有位次。

    在第一次搬家后,度度熊由于疏忽,又要求大家按照原指示进行了一次搬家。于是,机智的它想到:再按这个指示搬一次家不就可以恢复第一次搬家的样子了。于是,B厂史无前例的进行了连续三次搬家。

    虽然我们都知道度度熊的“机智”常常令人堪忧,但是不可思议的是,这回真的应验了。第三次搬家后的结果和第一次的结果完全相同。

    那么,有多少种指示会让这种事情发生呢?如果两种指示中至少有一个人的目标位置不同,就认为这两种指示是不相同的。

    Input

    第一行一个整数TT,表示T组数据。

    每组数据包含一个整数N(1 leq N leq 1 000 000)N(1N1000000)。

    Output

    对于每组数据,先输出一行 Case #i: 然后输出结果,对10000000071000000007取模。

    Sample Input
    2
    1
    3
    
    Sample Output
    Case #1:
    1
    Case #2:
    4

    思路:直接想的可能是怎么才能搬家三次的结果和搬家一次的结果相同,但是实际上可以简化为搬家两次的结果和搬家之前相同。
    要出现这样的情况,那第一次搬家i-j必须满足i==j或者i-j&&j-i(i!=j).
    当给定为N个人搬家时有ans[N]种满足条件的可能,假设第一个人是1-1,也就是位置不变,那么剩下的人可能的情况就是ans[N-1]种。
    当第一个人是1-j(j!=1)时,那么必定有j-1。所以还剩下N-2个人,所以就是ans[N-2],又因为此时j有N-1(2-N)种可能。
    可得出dp公式 ans[N] = ans[N-1] + (N-1)*ans[N-2];
    初始值ans[1] = 1;ans[2] = 2;利用递归或者dp即可解决,但是递归会超时,dp才是正道。
    import java.util.Scanner;
    
    public class Main{
        public static void main(String[] args){
            Scanner sc = new Scanner(System.in);
            while (sc.hasNext()) {
                int t = sc.nextInt();            
                long[] ans = new long[1000001];
                long mod = 1000000007l;
                ans[1] = 1;
                ans[2] = 2;
                int m = 3;
                for (int i = 1; i <= t; i++) {
                    int n = sc.nextInt();                
                    while (m <= n) {
                        ans[m] = mod(ans[m-1] + (m-1)*ans[m-2]); //用mod函数会稍微快点,但是不影响结果
                        m++;
                    }
                    System.out.println("Case #"+i+":");
                    System.out.println(ans[n]);
    //             System.out.println("Case #"+i+":
    "+ans[n]);//这种形式会报格式错误
                }
            }
        }
        public static long mod(long i) {
            return i%1000000007;
        }
    }
    
    /**TLE 递归影响时间,改成dp
    
    public static int find(int n) {
        if (n == 1)
            return 1;
        if (n == 2)
            return 2;
        else 
            return mod(find(n-1))+mod((n-1)*find(n-2));            
    }
    */
    
    
    
    

    代码中的斜体部分应该放到for循环外,这样就可以保存ans[]数组的结果,在下一个测试用例时就可以直接利用了,如果放在for循环内,就会超时。

    ps: 最后的打印换成代码中的另一种形式就会格式错误Presentation Error,不知道为什么。

     
  • 相关阅读:
    innerHTML与innerText区别
    HTML5中的数据集dataset和自定义属性data-*
    HTTP协议
    Javascript 中的 && 和 || 使用小结
    JavaScript 实现回文解码
    sublime中用less实现css预编译
    jQuery事件绑定的四种方法
    前端学习过程中需要看的书籍
    Echarts学习宝典
    Vue插槽
  • 原文地址:https://www.cnblogs.com/fisherinbox/p/5477272.html
Copyright © 2020-2023  润新知