蓝桥杯——试题 算法训练 Sereja and Squares

试题 算法训练 Sereja and Squares

资源限制
时间限制:4.0s 内存限制:256.0MB
问题描述
  Sereja在平面上画了n个点,点i在坐标(i,0)。然后,Sereja给每个点标上了一个小写或大写英文字母。Sereja不喜欢字母"x",
  ·所有的点可以被分成若干对,使得每个点恰好属于一一对之中。
  ·在每对点中,横坐标较小的会被标上小写字母,较大的会被标上对应的大写字母。
  ·如果我们在每对点上画一个正方形,其中已知的一对点会作为正方形的相对的顶点,它们间的线段会成为正方形的对角线,那么在所有画出的正方形中不会有
  小Petya擦掉了一些小写字母和所有大写字母,现在Sereja想知道有多少种方法来还原每个点上的字母,使得还原后这些点是漂亮的。
输入格式
  第一行是一个整数n,表示点的个数。
  第二行是一个长度为n的字符串,包含小写字母和问号"?",是按照横坐标递增的顺序的每个点的描述。问号表示这个点的字母被Petya擦掉了。保证输
输出格式
  输出答案对4294967296取模的值。如果没有可行的方案,输出0。
样例输入
4
a???
样例输出
50
样例输入
4
abc?
样例输出
0
样例输入
6
abc???
样例输出
1

数据规模和约定
  20个测试点的n分别为:
  5,10,20,50,100,
  200,500,1000,2000,5000,
  10000,20000,30000,40000,50000,
  60000,70000,80000,90000,100000.

解题思路:该问题与左右括号匹配原则相类似
解题算法:动态规划

Java 代码

import java.util.Scanner;

/**
 * @Author LZP
 * @Date 2021/2/17 16:18
 * @Version 1.0
 *
 * 动态规划
 * 
 * 试题 算法训练 Sereja and Squares
 * <p>
 * 资源限制
 * 时间限制:4.0s   内存限制:256.0MB
 * 问题描述
 *   Sereja在平面上画了n个点,点i在坐标(i,0)。然后,Sereja给每个点标上了一个小写或大写英文字母。Sereja不喜欢字母"x",所以他不用它标记点。Sereja认为这些点是漂亮的,当且仅当:
 *   ·所有的点可以被分成若干对,使得每个点恰好属于一一对之中。
 *   ·在每对点中,横坐标较小的会被标上小写字母,较大的会被标上对应的大写字母。
 *   ·如果我们在每对点上画一个正方形,其中已知的一对点会作为正方形的相对的顶点,它们间的线段会成为正方形的对角线,那么在所有画出的正方形中不会有相交或触碰的情况。
 *   小Petya擦掉了一些小写字母和所有大写字母,现在Sereja想知道有多少种方法来还原每个点上的字母,使得还原后这些点是漂亮的。
 * 输入格式
 *   第一行是一个整数n,表示点的个数。
 *   第二行是一个长度为n的字符串,包含小写字母和问号"?",是按照横坐标递增的顺序的每个点的描述。问号表示这个点的字母被Petya擦掉了。保证输入串不含字母"x"。
 * 输出格式
 *   输出答案对4294967296取模的值。如果没有可行的方案,输出0。
 * 样例输入
 * 4
 * a???
 * 样例输出
 * 50
 * 样例输入
 * 4
 * abc?
 * 样例输出
 * 0
 * 样例输入
 * 6
 * abc???
 * 样例输出
 * 1
 * <p>
 * 数据规模和约定
 *   20个测试点的n分别为:
 *   5,10,20,50,100,
 *   200,500,1000,2000,5000,
 *   10000,20000,30000,40000,50000,
 *   60000,70000,80000,90000,100000.
 * <p>
 * <p>
 * 递归实现:
 * 不过效率太低
 * 使用栈,将其当作左右括号匹配的问题来实现,这是本题的重要解题思路
 *
 * 本题适合用C++写,C++的效率比Java高太多了
 */
public class Main {

    private static long num = 0;

    private static long mod = 4294967296L;

    private static char[] arr = new char[100000 + 7];

    private static long[] dp = new long[100000 + 7];

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int n = input.nextInt();
        char[] tempChar = input.next().toCharArray();
        for (int i = 0; i < tempChar.length; i++) {
            arr[i + 1] = tempChar[i];
        }

        // 没有右括号 ,这一步一定不能漏
        dp[0] = 1;
        long start  = System.currentTimeMillis();
        f(arr, n);
        long end  = System.currentTimeMillis();
        System.out.println(num);
        System.out.println(end - start + "ms");
    }

    static void f(char[] arr, int n) {
        if (n % 2 == 1) {
            // 奇数直接返回
            return;
        }

        // 用来记录左括号的个数
        int left = 0;
        int n2 = n >> 1;

        // 循环从1~n
        for (int i = 1; i <= n; i++) {
            if (arr[i] == '?') {
                // 若是问好,则可能是左括号也可能是右括号
                // 确定右括号的最大上限
                int rightMax = i >> 1;
                if (i != n) {
                    /*
                        当dp到下标i时,可以确定的最多的右括号为i/2,
                        若现在确定的是左括号则前面i-1个格子就是确定rightMax个右括号的结果
                        若现在确定的是右括号,则前面i-1个格子确定的就是rightMax-1个右括号的结果
                     */
                    // 因为只是确定了右括号的最大上限,而不确定到底是多少,所以有很多种可能
                    // 每次后面多来一个右括号的话,前面的可能性就越多
                    for (; rightMax >= 1; rightMax--) {
                        dp[rightMax] += dp[rightMax - 1];
                        dp[rightMax] %= mod;
                    }
                } else {
                    //i==n一定是右括号,只要确定前n-1个格子所确定出来的n>>1-1个右括号的结果即可
                    dp[rightMax] = dp[rightMax - 1];
                    dp[rightMax] %= mod;
                }
            } else {
                // 若不是问好,则一定是左括号
                left++;
            }
        }

        if (left > n2) {
            // 左括号的个数超过一半
            return;
        }

        for (int i = 0; i < n2 - left; i++) {
            dp[n2] *= 25;
            dp[n2] %= mod;
        }
        num = dp[n2] % mod;
    }
}
posted @ 2021-02-18 09:07  没有你哪有我  阅读(142)  评论(0编辑  收藏  举报