本文正在参与掘金团队号上线活动,点击 查看大厂春招职位
一、题目描述:
今天要写的题来自 Codeforces,Codeforces 990C 括号序列连接问题:
题目大意: 你有 个字符串,其中有一些不是有效的括号序列,你可以在里面选择两个字符串 和 使得 是有效括号序列,现在让你找出这样的有序对 的个数。
输入: 第一行是字符串个数 。
之后的 行,每一行都包括一个字符串。
输出: 输出满足条件的有序对个数。
二、思路分析:
我们可以发现有效括号序列的性质:每一位前面的左括号个数大于等于右括号个数,并且总的左括号个数等于右括号个数。但是我们不可能将所有可能的字符串连接起来逐一判断,那样肯定会超时,所以我们就需要观察一下可以连接成有效序列的字符串的性质。
- 如果一个括号序列可以放左边,那么它的左括号个数可以大于右括号个数,因为可以由右边的括号序列补上;
- 如果一个括号序列可以放右边,那么对于它的每一位都必然有前面的右括号个数大于等于左括号个数,并且多出的右括号个数与左边的括号序列多出的左括号个数相等。
基于这两条性质我们可以将不同的字符串进行分组,用 表示可作为左边序列且左括号个数比右括号个数多 个的字符串个数, 表示可作为右边序列且右括号个数比左括号个数多 个的字符串个数,那么答案就等于 。
三、AC 代码:
#include <iostream>
using namespace std;
using ll = long long;
const int maxn = 3e5 + 5;
// 记录每种可作为左边或右边的括号序列个数
ll lft[maxn], rht[maxn];
// 计算左括号与右括号个数的差值
inline int count(const string &s) {
int cnt0 = 0, cnt1 = 0;
for (auto &ch : s) if (ch == '(') cnt0++; else cnt1++;
return abs(cnt0 - cnt1);
}
// 判断是否可作为左边的括号序列
inline bool isLeft(const string &s) {
int cnt0 = 0, cnt1 = 0;
for (int i = 0; i < s.length(); ++i) {
if (s[i] == '(') cnt0++; else cnt1++;
if (cnt1 > cnt0) return false;
}
return true;
}
// 判断是否可作为右边的括号序列
inline bool isRight(const string &s) {
int cnt0 = 0, cnt1 = 0;
for (int i = s.length() - 1; i >= 0; --i) {
if (s[i] == '(') cnt0++; else cnt1++;
if (cnt1 < cnt0) return false;
}
return true;
}
int main() {
int n;
cin >> n;
string s;
for (int i = 0; i < n; ++i) {
cin >> s;
if (isLeft(s)) {
int cnt = count(s);
lft[cnt]++;
// 如果差值为 0,说明即可作左边的序列也可以作为右边的序列
if (cnt == 0) rht[0]++;
} else if (isRight(s)) {
int cnt = count(s);
rht[cnt]++;
}
}
ll ans = 0;
for (int i = 0; i < maxn; ++i) ans += lft[i] * rht[i];
printf("%lld", ans);
}
复制代码
四、总结:
一些看起来很复杂的题目往往可以使用一些性质来化简,这道题就是如此,如果一个一个判断显然不行,而如果我们将字符串分类讨论,解题就简单多了,而这只不过比暴力解法多了一步总结规律罢了。