## 密文板
#贪心 #构造
这题看起来就很像贪心构造答案。
首先我们把可以匹配的左右括号都先匹配上,具体思路是从左往右枚举,对于每个右括号,如果左边有还没有匹配的左括号,那么就匹配上。
接下来考虑一个问号和一个括号匹配。一种是问号和右括号匹配。由于和上面进行初始匹配一样都需要枚举右括号,所以可以一起处理。然后是左括号和问号匹配,正着或反着枚举均可,但是正着枚举必须选大于当前下标的最小下标的问号,反着则不需要。
由于我们有些左括号已经在之前跟右括号匹配了,而这里和问号匹配当然是越靠左越好。所以最开始我们找和右括号匹配的左括号需要优先选下标更大的(栈实现),这样才使得在可以留下左括号的情况下,左括号都尽量靠左,可以与更多右边的问号匹配。
最后是剩下的问号自己和自己匹配。按照下标从小到大的顺序一左一右即可。
维护所有的问号我是用了 set。可以快速找小于/大于某个下标的问号,并且支持删除。时间复杂度 $\mathcal O(n\log_2 n)$。
```cpp
#include <fstream>
#include <stack>
#include <set>
using namespace std;
const int kMaxN = 1e5 + 5;
ifstream cin("cipher.in");
ofstream cout("cipher.out");
int f[kMaxN], t, n;
set<int> s;
stack<int> q;
char a[kMaxN];
int main() {
cin.tie(0)->sync_with_stdio(0);
for (cin >> t; t; t--) {
cin >> n >> a;
for (int i = n; i >= 1; i--) {
a[i] = a[i - 1];
}
fill(f + 1, f + n + 1, 0);
for (; q.size(); q.pop()) { }
s.clear();
int ans = 0;
for (int i = 1; i <= n; i++) {
if (a[i] == '?') {
s.insert(i);
}
}
for (int i = 1; i <= n; i++) {
if (a[i] == '(') {
q.push(i);
} else if (a[i] == ')') {
if (q.size()) {
f[q.top()] = f[i] = 1;
q.pop();
ans++;
continue;
}
auto p = s.lower_bound(i);
if (p != s.begin()) {
p--;
f[*p] = f[i] = 1;
a[*p] = '(';
s.erase(p);
ans++;
}
}
}
for (int i = n; i >= 1; i--) {
if (a[i] == '(' && !f[i]) {
auto p = s.upper_bound(i);
if (p != s.end()) {
s.erase(p);
a[*p] = ')';
ans++;
}
}
}
bool b = 1;
for (int i : s) {
a[i] = b ? '(' : ')';
ans += !b;
b ^= 1;
}
cout << n - 2 * ans << '\n' << (a + 1) << '\n';
}
return 0;
}
```
## 括号序列
#状压dp #二分 #前缀和
首先看到 $n\le 20$,这很有可能是状压的题目。然后再看到括号序列,那我们就可以构建一个前缀和数组,左括号减一右括号加一,对于一段前缀和都不小于 $0$ 的前缀,有多少个 $0$ 就代表着有多少个合法括号串前缀。
然后我就设计出了状态:$dp_{i,j}$ 表示若 $n$ 个串的已选状态为 $i$,最后的前缀值为 $j$ 所能获得的最大答案。当时我觉得这个状态会炸,因为 $j$ 的取值特别多,但是实际上 $j$ 只有一种取值,即 $i$ 包含的所有 $s_i$ 的前缀和值相加的和。不过我赛时还是写了个 `map` 就是了。
枚举已选状态 $i$ 之后,再枚举新的要选的 $j$。如何判断拼上 $j$ 了之后是否存在一段前缀和小于 $0$ 呢?可以对每个串的前缀和值再做一个前缀 $\min$,这样就可以快速判断整个串。如果整个串拼上来不会导致有小于 $0$ 的,那么就可以直接转移 dp。
对于有小于 $0$ 的,拼完这一段继续拼任何东西都不会有任何加成了。所以我们通过那个前缀 $\min$ 的数组做二分,找到最大的 $p$ 使得前 $p$ 个数都不会导致前缀值变到负数。二分完我们还需要求一个前缀当中有多少个指定的数。每个字符串开 $2\times|s_i|+1$ 个 `vector` 存前缀值所对应的下标(要加偏移量应对负数),在下标 `vector` 上二分即可。
时间复杂度 $\mathcal O\left(2^n\left(n+\sum\limits_{i=1}^n\log|s_i|\right)\right)$。当然我瞎写了 `map` 常数较大。
- [!] 注意查询一定要判越界,这就是我在初测是挂 $60$ 分的原因。
- [c] 一个悲伤的故事是,由于我把 `<fstream>` 的 `f` 写成了大写 `F` 直接导致我这题在 Linux 上 CE 保灵。**警钟长鸣🔔**
```cpp
#include <fstream> // 不要写成 Fstream
#include <algorithm>
#include <vector>
#include <map>
using namespace std;
const int kMaxN = 21;
ifstream cin("seq.in");
ofstream cout("seq.out");
int n, ans;
string s[kMaxN];
vector<int> p[kMaxN], m[kMaxN];
vector<vector<int>> v[kMaxN];
map<int, int> dp[1 << kMaxN];
vector<int> &get(int i, int j) {
static vector<int> empty;
if (j + s[i].size() < 0 || j + s[i].size() >= v[i].size()) { // 越界
return empty;
}
return v[i][j + s[i].size()];
}
int main() {
cin.tie(0)->sync_with_stdio(0);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> s[i];
int pre = 0, mn = 1e9;
p[i].reserve(s[i].size());
m[i].reserve(s[i].size());
v[i].resize(2 * s[i].size() + 1);
for (int j = 0; j < s[i].size(); j++) {
pre += (s[i][j] == '(' ? 1 : -1);
mn = min(mn, pre);
p[i].push_back(pre);
m[i].push_back(mn);
get(i, pre).push_back(j);
}
}
dp[0][0] = 0;
for (int i = 0; i < (1 << n); i++) {
for (const auto &j : dp[i]) {
for (int k = 1; k <= n; k++) {
if (i & (1 << (k - 1))) {
continue;
}
if (j.first + m[k].back() >= 0) {
int &x = dp[i | (1 << (k - 1))][j.first + p[k].back()];
int val = j.second + get(k, -j.first).size();
x = max(x, val);
ans = max(ans, val);
continue;
}
int p = -1;
for (int l = 0, r = s[k].size() - 1; l <= r; ) {
int mid = (l + r) / 2;
if (j.first + m[k][mid] >= 0) {
p = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
if (p != -1) {
auto x = get(k, -j.first);
int val = upper_bound(x.begin(), x.end(), p) - x.begin();
ans = max<int>(ans, j.second + val);
}
}
}
}
cout << ans << '\n';
return 0;
}
```
## 法国
#暴力 #数学
记 $a=att,b=hp,A=Att,B=Hp$.
首先我们看到这个式子 $\sum\limits_{i=1}^k\left\lceil\cfrac{b_i}{A_q}\right\rceil a_i\ge B_q$。我靠,怎么这么复杂,而且随着 ${} q$ 的增大,$A_q$ 会增大,这个分数会变小这也太难维护了吧?但是仔细想想,分子 $b_i$ 是固定的,分母单调递增,而一个数 $x$ 除以任意正数(上或下取整均可),不同的答案它的量级貌似是 $\mathcal O\left(\sqrt x\right)$ 级别的(实测 ${} x=3\times 10^5 {}$ 时大约有 $1000$ 个),所以我们只需要在分数 $\left\lceil\cfrac{b_i}{A_q}\right\rceil$ 的值发生变化是重新计算贡献即可。这样子时间复杂度就是 $\mathcal O(n\sqrt V)$ 的了,完全可行。
现在我们考虑当 $A_q$ 增大到多少的时候分数值会发生改变,即解关于 $x$ 的方程 $\left\lceil\cfrac{a}{x}\right\rceil<b$。根据赤石的推导,可以得到 ${} x\ge\left\lceil\cfrac{a}{b-1}\right\rceil {}$,这里 $a=b_i,b=\left\lceil\cfrac{b_i}{A_q}\right\rceil$。所以当到后面最小的满足 $A_q\ge x$ 的地方,分数值才会改变。
由于本题值域只有 $3\times 10^5$,所以我们直接把 $x$ 当下标,将 $i$ 的值存到 `vector` 里面。再维护一个指针 $k$ 用来遍历重构,每次把 $k$ 移动到 $\le A_q$ 的所有值上,从 `vector` 里面取出下标重新计算贡献,计算完之后再算出下一次重算贡献需要的 $A_q$ 值。由于每个元素重算贡献的时间复杂度为 $\sqrt V$,所以复杂度满足要求。
注意当分数值已经等于 $1$ 的时候就不要放进 `vector` 里面了,一个是它的值不会改变(一直都是 $1$),一个是计算需要除以它 $-1$ 的值,直接导致 RE。
```cpp
#include <fstream>
#include <vector>
#include <queue>
using namespace std;
using LL = long long;
const LL kMaxN = 3e5 + 5;
ifstream cin("france.in");
ofstream cout("france.out");
LL a1[kMaxN], b1[kMaxN], a2[kMaxN], b2[kMaxN], f[kMaxN], n, m, v, sum;
vector<int> vct[kMaxN];
priority_queue<pair<LL, LL>, vector<pair<LL, LL>>, greater<>> q;
LL ceil(LL a, LL b) {
return (a + b - 1) / b;
}
int main() {
cin.tie(0)->sync_with_stdio(0);
cin >> n >> m >> v;
for (LL i = 1; i <= n; i++) {
cin >> a1[i] >> b1[i];
}
for (LL i = 1; i <= m; i++) {
cin >> a2[i] >> b2[i];
}
for (LL i = 1, j = 1, k = 1; i <= m; i++) {
for (; k <= a2[i]; k++) {
for (int j : vct[k]) {
sum -= f[j];
LL x = ceil(b1[j], a2[i]);
sum += x * a1[j];
f[j] = x * a1[j];
if (x > 1) {
vct[ceil(b1[j], x - 1)].push_back(j);
}
}
vct[k].clear();
vct[k].shrink_to_fit(); // 清内存
}
for (; j <= n && sum < b2[i]; j++) {
LL x = ceil(b1[j], a2[i]);
sum += x * a1[j];
f[j] = x * a1[j];
if (x > 1) {
vct[ceil(b1[j], x - 1)].push_back(j); // 大于它 整除值就会改变
}
}
cout << (sum >= b2[i] ? j - 1 : -1) << '\n';
}
return 0;
}
```
## 酒杯
#组合数学 #概率与期望
赛时我两种暴力都 WA 了,都没有调出来,太悲惨了。
首先是对于 $n,m\le 500$,考虑 $nm^2$ 的 DP。设 $dp_{i,j}$ 表示前 $i$ 层已经有了 $j$ 个 AC,那么枚举下一层有了 $k$ 个 AC 计算概率转移即可。注意答案需要额外乘上 $m!$ 因为 $m$ 个 AC 位置可以互换。
对于 $n\le 20$ 但 $m$ 特别大的数据,考虑枚举有哪几层没有任何一个 AC,计算概率后加起来,再用 $1$ 减去他们,得到任意一层都有 AC 的概率。