蓝桥杯——试题 算法训练 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;
}
}