1 条题解
-
0
CF2072B 题解
1. 题目分析
题意简述
给定一个只含
-和_的字符串,可以任意重排字符。重排后,统计等于-_-的不同子序列个数,要求这个数量最大。难点剖析
子序列
-_-需要两个-和一个_,并且_必须位于这两个-中间。重排时我们只需要关心两类字符的数量,而不用关心原字符串顺序。设
-的数量为 ,_的数量为 。如果某个_左边有 个-,右边有 个-,那么它作为中间字符可以贡献 个-_-子序列。2. 从暴力到正解
策略一:小数据暴力 / 部分分
当 很小时,可以枚举所有不同的重排,然后对每个重排统计
-_-子序列数量,取最大值。统计一个固定字符串时,可以枚举中间的
_,计算它左边和右边的-数量,累加贡献。这种做法的复杂度主要来自重排枚举,最坏接近 ,只能处理很小的 。
策略二:观察贡献形式
暴力的瓶颈在于枚举了大量本质相同的排列。实际上,对于每个
_来说,它的贡献只由左侧-数量和右侧-数量决定。为了让所有
_的贡献尽可能大,最优策略是把所有_放在一起,并放在两段-中间:-----_____----此时每个
_的贡献都相同,等于左侧-数量乘右侧-数量。剩下的问题变成:把 个
$$L = \left\lfloor \frac c2 \right\rfloor,\quad R = \left\lceil \frac c2 \right\rceil$$-分成左右两组,最大化 。当两边尽量平均时乘积最大,所以:3. 正解思路
如何想到正解
目标子序列固定为
-_-,中间字符只能是_。因此先固定一个_,它能和左边任意一个-、右边任意一个-组成答案。要最大化总数,就要让每个_都拥有尽可能多的左右-组合。把
_分散开不会比把它们集中在同一个最佳位置更优,因为每个_都希望同样的左右-划分达到最大乘积。算法步骤
- 统计字符串中
-的数量 和_的数量 。 - 将 个
-尽量平均分成左右两边:- 左边数量为 ;
- 右边数量为 。
- 每个
_的最大贡献为左右数量乘积。 - 总答案为:
正确性说明
对于任意排列,设某个
_左边有 个-,右边有 个-,则 ,该_的贡献为 。在 固定时, 在两数尽量接近时最大。因此单个
_的贡献不超过 $\left\lfloor c/2 \right\rfloor\left\lceil c/2 \right\rceil$。所有_的总贡献也不超过这个上界乘以 。将所有
_放在两段尽量均分的-中间时,每个_都恰好达到这个最大贡献,所以上界可以取到,算法正确。复杂度分析
每组测试用例只需要扫描一次字符串。
- 时间复杂度:。
- 空间复杂度:。
4. 参考代码(C++)
#include <bits/stdc++.h> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int T; cin >> T; while (T--) { int n; string s; cin >> n >> s; // 统计两种字符的数量。 long long dash = 0, under = 0; for (char ch : s) { if (ch == '-') { ++dash; } else { ++under; } } // 把所有 '-' 尽量平均分到 '_' 的左右两侧。 long long left = dash / 2; long long right = dash - left; // 每个 '_' 都贡献 left * right 个 "-_-" 子序列。 cout << under * left * right << '\n'; } return 0; } - 统计字符串中
- 1
信息
- ID
- 169
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 2
- 标签
- 递交数
- 20
- 已通过
- 0
- 上传者