You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

181 lines
5.6 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package class_2023_05_2_week;
// 来自学员问题,来自真实笔试
// 塔子哥最近在处理一些字符串相关的任务
// 他喜欢 R 字符,因为在某些任务中,这个字符通常表示“正确”的结果
// 另一方面,他不喜欢 B 字符,因为在某些任务中,这个字符通常表示“错误”的结果
// 为了解决他的任务,塔子哥定义了字符串的权值为字符串中 R 字符的出现次数
// 例如,对于字符串 BBRBRB它的权值为 2因为其中有 2 个 R 字符
// 现在,塔子哥面临一个问题,他有一个长度为 n 的字符串 s它仅由 R 和 B 组成
// 他想知道,长度为 n 的仅由 R 和 B组成的字符串中
// 字典序不小于 s 的字符串的权值之和是多少?
// 因此,他需要编写一个程序来解决这个问题
// 输入第一行为一个整数 n ,表示字符串的长度
// 输入第二行为一个长度为 n 的字符串 s ,字符串中元素组成仅为 R 和 B
// 输出一个整数,代表长度为 n 的、字典序不小于 s 的字符串权值之和
// 输入样例:
// 3
// RBR
// 输出:
// 7
// 解释:共有 3 个字符串字典序大于等于"RBR"RBR权值为2RRB为2RRR为3
// 1 <= n <= 100000
// 结果可能很大对1000000007取模
// 帖子链接 : https://www.mashibing.com/question/detail/67223
public class Code02_LexicographicBiggerSumOfR {
// 为了测试
// 暴力方法
public static int sum1(String str) {
return process1("", str);
}
// 为了测试
// 暴力方法
public static int process1(String path, String s) {
if (path.length() == s.length()) {
if (path.compareTo(s) >= 0) {
int ans = 0;
for (int i = 0; i < path.length(); i++) {
if (path.charAt(i) == 'R') {
ans++;
}
}
return ans;
} else {
return 0;
}
} else {
return process1(path + "R", s) + process1(path + "B", s);
}
}
// 以下为正式方法
public static int MAXN = 100001;
// pow2[i] : 长度为i的所有RB串一共有多少字符串%mod的余数
// 一定要求余数!
public static int[] pow2 = new int[MAXN];
// f[i] : 长度为i的所有RB串一共有多少权值和%mod的余数
// 一定要求余数!
public static int[] f = new int[MAXN];
public static int mod = 1000000007;
static {
pow2[0] = 1;
for (int i = 1; i < MAXN; i++) {
pow2[i] = (pow2[i - 1] * 2) % mod;
}
f[1] = 1;
for (int i = 2; i < MAXN; i++) {
// 2^i-1 + 2*f[i-1]
f[i] = (pow2[i - 1] + f[i - 1]) % mod;
f[i] = (f[i] + f[i - 1]) % mod;
}
}
// 普通递归版
// 不是最优解,但是展示了大过程
public static int sum2(String str) {
int n = str.length();
char[] s = str.toCharArray();
int[] rnumber = new int[n];
rnumber[0] = s[0] == 'R' ? 1 : 0;
for (int i = 1; i < n; i++) {
rnumber[i] = rnumber[i - 1] + (s[i] == 'R' ? 1 : 0);
}
return process2(s, rnumber, n, 0);
}
// 你依次填写字符串
// 0...i-1范围上你填写的东西和s完全一样
// 当前来到i位置一共的长度是n
// rnumber[i] : s[0...i]范围上有几个R
// 返回 : 在[0...i-1]和s完全一样的情况下后续所有字典序不小于s的字符串整体的权值和是多少?
public static int process2(char[] s, int[] rnumber, int n, int i) {
int ans = 0;
if (i == n) {
ans = rnumber[n - 1];
} else {
if (s[i] == 'B') {
// s当前位置是'B'你可以填写R也可以是B
// 如果你当前填R现在的情况是
// 0...i-1位置上你填写的和s一样
// i位置上s是'B',你填写的是'R'
// i之后的剩余长度是 : n-i-1你可以随便填了
// n-i-1可以随便填那么字符串个数为 : 2^(n-i-1)个
// 这2^(n-i-1)个字符串,都是:
// [0...i-1]和s一样i位置比s多一个R
// 权值和 = 1) + 2)
// 1) 是这2^(n-i-1)个字符串,前缀上的权值和
// 2) 是这2^(n-i-1)个字符串,后缀上的权值和
// 具体来说 :
// 1) (s的[0...i]范围上R的数量 + 1) * 2^(n-i-1)
// 2) 在[i+1....]范围上的权值和 : f[n-i-1]
int p1 = (int) (((long) (rnumber[i] + 1) * pow2[n - i - 1]) % mod);
p1 = (p1 + f[n - i - 1]) % mod;
// 如果你当前填B那么继续递归
int p2 = process2(s, rnumber, n, i + 1);
ans = (p1 + p2) % mod;
} else {
// s当前位置是'R'你只能填写R然后继续递归
ans = process2(s, rnumber, n, i + 1);
}
}
return ans;
}
// 彻底动态规划版
// 正式版时间复杂度O(N)
public static int sum3(String str) {
int n = str.length();
char[] s = str.toCharArray();
int[] rnumber = new int[n];
rnumber[0] = s[0] == 'R' ? 1 : 0;
for (int i = 1; i < n; i++) {
rnumber[i] = rnumber[i - 1] + (s[i] == 'R' ? 1 : 0);
}
int[] dp = new int[n + 1];
dp[n] = rnumber[n - 1];
for (int i = n - 1; i >= 0; i--) {
if (s[i] == 'B') {
int p1 = (int) (((long) (rnumber[i] + 1) * pow2[n - i - 1]) % mod);
p1 = (p1 + f[n - i - 1]) % mod;
int p2 = dp[i + 1];
dp[i] = (p1 + p2) % mod;
} else {
dp[i] = dp[i + 1];
}
}
return dp[0];
}
// 为了测试
public static String randomString(int n) {
char[] s = new char[n];
for (int i = 0; i < n; i++) {
s[i] = Math.random() < 0.5 ? 'B' : 'R';
}
return String.valueOf(s);
}
public static void main(String[] args) {
int N = 15;
int testTimes = 10000;
System.out.println("测试开始");
for (int i = 0; i < testTimes; i++) {
int n = (int) (Math.random() * N) + 1;
String s = randomString(n);
int ans1 = sum1(s);
int ans3 = sum3(s);
if (ans1 != ans3) {
System.out.println("出错了!");
}
}
System.out.println("测试结束");
}
}