题解:学而思编程 真空扫地机器人
【题目来源】
学而思编程:真空扫地机器人
【题目描述】
Imp家里有一个扫地机器人,他还养了一条狗。扫地机器人工作时会发出很多噪音,狗很害怕这些噪音。
有一天,Imp提取了机器人的运行记录,这些记录是 \(n\) 个字符串 \(t_1,t_2,...,t_n\),每个字符串只包含英文字母s和h。但是他无法知道这些记录的时间先后顺序,他将这些字符串连接成一个字符串 \(T\),使 \(T\) 中子序列sh的个数最多。
噪音的数量定义为:字符串中子序列sh的个数。准确地说,是满足 \(i<j\) 且 \(T_i=s,T_j=h\) 的 \((i,j)\) 个数。
【输入】
第 \(1\) 行,\(1\) 个正整数 \(n\)
接下来 \(n\) 行,每行 \(1\) 个字符串 \(t_i\)
【输出】
输出 \(1\) 个整数,为最大的噪音数量。
【输入样例】
4
ssh
hs
s
hhhs
【输出样例】
18
【核心思想】
-
问题分析:给定 \(n\) 个字符串,每个字符串只包含 's' 和 'h'。需要将这些字符串按某种顺序拼接,使得拼接后的字符串中 "sh" 子序列(即前面的 's' 和后面的 'h' 组成的对)的总数最大化。这是一个自定义排序贪心 + 前缀和统计问题,关键在于找到最优的字符串拼接顺序。
-
算法选择:
- 自定义排序规则:对于两个字符串 \(a\) 和 \(b\),如果 \(a\) 放在 \(b\) 前面产生的 "sh" 对数更多,则 \(a\) 排在前面
- 排序规则推导:设字符串 \(a\) 有 \(s_a\) 个 's' 和 \(h_a\) 个 'h',字符串 \(b\) 有 \(s_b\) 个 's' 和 \(h_b\) 个 'h'
- \(a\) 放前面时贡献:\(s_a \times h_b\)(\(a\) 中的 's' 与 \(b\) 中的 'h' 配对)
- \(b\) 放前面时贡献:\(s_b \times h_a\)(\(b\) 中的 's' 与 \(a\) 中的 'h' 配对)
- 比较:若 \(s_a \times h_b > s_b \times h_a\),则 \(a\) 应排在 \(b\) 前面
- 前缀和统计:拼接后遍历字符串,维护前缀 's' 的数量,遇到 'h' 时将当前前缀 's' 的数量累加到答案
-
关键步骤:
- 读取输入:\(n\)(字符串数量)、\(n\) 个字符串 \(t_i\)
- 统计字符:对每个字符串统计 's' 的数量 \(s_i\) 和 'h' 的数量 \(h_i\)
- 自定义排序:按 \(s_i \times h_j > s_j \times h_i\) 的规则排序
- 拼接字符串:按排序后的顺序拼接所有字符串
- 前缀和统计(遍历拼接后的字符串):
- 遇到 's':
sa[i] = sa[i-1] + 1(更新前缀 's' 的数量) - 遇到 'h':
sa[i] = sa[i-1],ans += sa[i](累加当前 'h' 前面的 's' 总数)
- 遇到 's':
- 输出结果:最大 "sh" 子序列对数 \(ans\)
-
时间/空间复杂度:
- 时间复杂度:\(O(n \times L \log n)\),\(L\) 为字符串平均长度,主要是排序和统计的复杂度
- 空间复杂度:\(O(n \times L)\),用于存储字符串
-
自定义排序贪心 + 前缀和的核心思想:
- 交换论证:通过比较相邻两个字符串交换位置后的贡献,推导出最优排序规则
- 排序规则:按 \(s \times h\) 的交叉乘积比较,确保产生更多 "sh" 对的字符串排在前面
- 前缀和优化:拼接后只需一次线性扫描,用前缀和维护 's' 的数量,高效统计 "sh" 对数
- 贪心正确性:自定义排序确保任意相邻两个字符串的顺序都是最优的,从而整体顺序最优
- 适用于字符串拼接优化、顺序相关最大化问题、子序列统计类问题
【算法标签】
贪心
【代码详解】
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
#define int long long
const int N = 1e5 + 5;
int n, sa[N], ans; // sa: 前缀s的个数, ans: 最终答案
struct node
{
int s, h; // s: 字符串中's'的数量, h: 字符串中'h'的数量
string c; // 原始字符串
} a[N];
// 自定义排序规则:比较s1/h1和s2/h2的大小
bool cmp(node x, node y)
{
return x.s * y.h > y.s * x.h;
}
signed main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i].c;
// 统计每个字符串中's'和'h'的数量
for (int j = 0; j < a[i].c.size(); j++)
{
if (a[i].c[j] == 's')
{
a[i].s++;
}
else
{
a[i].h++;
}
}
}
sort(a + 1, a + n + 1, cmp); // 按规则排序
// 将排序后的字符串拼接
string s = " ";
for (int i = 1; i <= n; i++)
{
s += a[i].c;
}
// 计算答案:对于每个'h',统计它前面的's'个数
for (int i = 1; i < s.size(); i++)
{
if (s[i] == 's')
{
sa[i] = sa[i - 1] + 1; // 更新前缀's'的数量
}
else
{
sa[i] = sa[i - 1]; // 遇到'h'时不增加计数
ans += sa[i]; // 累加当前'h'前面的's'总数
}
}
cout << ans;
return 0;
}
【运行结果】
4
ssh
hs
s
hhhs
18
浙公网安备 33010602011771号