- P195's solution
P195's Solution
- 2025-9-4 22:00:12 @
Part 1
首先,通过模拟,不难发现:开奖过程实际上是在环形字符串 中截取子串,并取第 大字母(设 )。
不难发现,最难处理的就是「第 大」。于是,我们需要一条重要性质:
在一个含有不少于 个数的序列中,任意一个数 成为第 大数的充分必要条件是:
设序列中大于 的数共 个,大于等于 的数共 个,则有 。
这个性质很显然,证明不给了。
Part 2
注意到 ( 中最多有 种字符),我们将每个字符分开处理。
先将 复制一遍接到末尾。
对于某个字符 ,设 为 ( 的前 个字符)中大于 的字符个数, 为 ( 的前 个字符)中大于等于 的字符个数。
显然,它们可以用前缀和处理,并且单调不降。
然后,枚举每个 ,依据上面性质查找符合要求的最小 与最大 ,合法的 即为两者之间的数,可以 统计答案。注意 。
可以发现,如果升序枚举 ,最小 与最大 单调不降,于是每次都可以从上次的位置接着升序枚举。
因此,每个字符可以做到 求解。总时间复杂度 ,空间复杂度 。
Part 3
std:
#include <iostream>
using namespace std;
const int PP = 2000005;
int l[PP], le[PP], pp[PP];
// s[i] t[i] P
int p, k;
int lastf1 = 0, lastf2 = 0; // 上次查询的记录
int f1(int lf, int rg, int v) { // 最大 r:s <= k - 1
if (l[lf] > v) return lf - 1; // 没有符合要求的 r
while (l[lastf1 + 1] <= v && lastf1 < rg) lastf1 ++;
return lastf1;
}
int f2(int lf, int rg, int v) { // 最小 r:t >= k
if (le[rg] < v) return rg + 1; // 没有符合要求的 r
while (le[lastf2] < v && lastf2 < rg) lastf2 ++;
return lastf2;
}
int main() {
cin >> p >> k;
for (int i = 1; i <= p; i ++) {
char c;
cin >> c;
pp[i] = pp[i + p] = c - 64; // 读入 + 复制 + A,B,C,...,Z 转为 1,2,3,...,26
}
for (int cases = 1; cases <= 26; cases ++) {
lastf1 = lastf2 = 0;
for (int i = 1; i <= p * 2; i ++) {
l[i] = l[i - 1];
le[i] = le[i - 1];
if (pp[i] < cases) l[i] ++;
if (pp[i] <= cases) le[i] ++;
}
long long ans = 0;
for (int i = 1; i <= p; i ++)
ans += max(f1(i + k - 1, i + p - 1, k + l[i - 1] - 1) - f2(i + k - 1, i + p - 1, k + le[i - 1]) + 1, 0);
cout << ans << endl;
}
return 0;
}