ARC170 补
A Yet Another AB Problem
给定两个长度为 \(n\) 的 AB 字符串 \(s\) 和 \(t\),问最少对 \(s\) 进行多少次操作可以时期与 \(t\) 相同。
操作:选择一对 \(1\leq i<j\leq n\),使 \(s_i\Leftarrow A\) 且 \(s_j\Leftarrow B\)。
Solution
一个位置要变 B 它前面必须有 A;反之如果要变 A 则其后面必须有 B。否则无解。
此时实际上已经构造出一种方案了。但是要使其操作数最少,可以让前面变 A 后面变 B 的一次性解决。这样操作数一定最少。
\(\\\)
Code
// STOOOOOOOOOOOOOOOOOOOOOOOOO hzt CCCCCCCCCCCCCCCCCCCCCCCORZ
#include <algorithm>
#include <iostream>
#include <numeric>
#include <vector>
using namespace std;
using LL = long long;
using PII = pair<int, int>;
constexpr int kN = 2e5 + 1;
int n;
string s, t;
int main() {
cin.tie(0)->sync_with_stdio(0);
cin >> n >> s >> t;
for (int i = 0; i < n && t[i] != 'A'; i++) {
if (s[i] == 'A') {
cout << "-1\n";
return 0;
}
}
for (int i = n - 1; i >= 0 && t[i] != 'B'; i--) {
if (s[i] == 'B') {
cout << "-1\n";
return 0;
}
}
int ans = 0;
for (int i = 0; i < n; i++)
ans += s[i] != t[i];
for (int i = 0, a = 0; i < n; i++) {
if (s[i] == t[i])
continue;
a += t[i] == 'A';
if (t[i] == 'B' && a > 0)
a--, ans--;
}
cout << ans << '\n';
return 0;
}
B Arithmetic Progression Subsequence
给定一个长度为 \(n\) 值域 \([1,10]\) 的序列 \(a\),问有多少个区间满足存在两个位置之间有他们的平均数。
Solution
显然只需要对于每个左端点 \(l\) 找出最小的右端点 \(r\),比 \(r\) 大的显然都能满足条件。
于是可以双指针从左往右推,一直加数直到存在这样的数对。这个数对的寻找可以通过枚举值找位置,再看存不存在平均数在这两个位置中间。时间复杂度 \(O(V^2n\log n)\)。
\(\\\)
Code
// STOOOOOOOOOOOOOOOOOOOOOOOOO hzt CCCCCCCCCCCCCCCCCCCCCCCORZ
#include <algorithm>
#include <iostream>
#include <numeric>
#include <set>
#include <vector>
using namespace std;
using LL = long long;
using PII = pair<int, int>;
constexpr int kN = 2e5 + 1, kV = 10 + 1;
int n, a[kN];
set<int> s[kV];
bool Check() {
for (int i = 1; i <= 10; i++) {
for (int j = 1; j <= 10; j++) {
if (s[i].empty() || s[j].empty() || (i + j) % 2 == 1)
continue;
int pi = *s[i].begin(), pj = *s[j].rbegin(), m = (i + j) / 2;
auto p = s[m].upper_bound(pi);
if (p != s[m].end() && *p < pj)
return 1;
}
}
return 0;
}
int main() {
cin.tie(0)->sync_with_stdio(0);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
LL ans = 0;
for (int i = 1, j = 0; i <= n; i++) {
for (; j <= n && !Check(); j++, s[a[j]].insert(j));
ans += n - j + 1;
// cerr << i << ' ' << j << '\n';
s[a[i]].erase(i);
}
cout << ans << '\n';
return 0;
}
C Prefix Mex Sequence
给定一个长度为 \(n\) 的 01 序列 \(s\),求有多少个长度为 \(n\) 值域 \([0,m]\) 的序列 \(a\) 满足条件。
条件:对于所有 \(i\in[1,n]\),若 \(s_i=0\),则 \(a_i\neq\mathop{mex}\limits_{j=1}^{i-1}a_j\);否则 \(a_i= \mathop{mex}\limits_{j=1}^{i-1}a_j\)。
Solution
显然 \(m\geq n\) 时,总有数可以填,mex 永远不超过值域。遇 \(s_i=1\) 有 \(m\) 种填法,\(s_i=0\) 只能有一种。
而 \(m<n\) 可能出现 mex 填不下,需要限定一些重叠。不如考虑 dp。无论你选了哪些填显然不影响你 mex 只有一个,所以我们只关心填不同数的个数。因此设 \(f_{i,j}\) 表示考虑到第 \(i\) 个位置,填了 \(j\) 个不同的数。
若 \(s_i=0\),则 \(f_{i,j}=f_{i-1}{j-1}\)。
若 \(s_i=1\),则 \(f_{i,j}=j\times f_{i-1,j}+(m-j+1)f_{i-1,j-1}\),即填 填过/没填过 的数。
\(\\\)
Code
// STOOOOOOOOOOOOOOOOOOOOOOOOO hzt CCCCCCCCCCCCCCCCCCCCCCCORZ
#include <algorithm>
#include <atcoder/all>
#include <iostream>
#include <numeric>
#include <vector>
using namespace std;
using namespace atcoder;
using LL = long long;
using mint = modint998244353;
using PII = pair<int, int>;
constexpr int kN = 5e3 + 1;
int n, m, s[kN];
mint ans, f[kN][kN];
int main() {
cin.tie(0)->sync_with_stdio(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> s[i];
}
if (m >= n) {
ans = 1;
for (int i = 1; i <= n; i++) {
if (s[i] == 0)
ans *= m;
}
cout << ans.val() << '\n';
} else {
f[0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m + 1; j++) {
if (s[i] == 1)
f[i][j] = f[i - 1][j - 1];
else
f[i][j] = j * f[i - 1][j] + (m - j + 1) * f[i - 1][j - 1];
}
}
for (int i = 1; i <= m + 1; i++)
ans += f[n][i];
cout << ans.val() << '\n';
}
return 0;
}
D Triangle Card Game
Alice & Bob 手中分别有 \(n\) 张牌 \(a\) 和 \(b\)。轮流出牌,Alice 2 张 Bob 1 张。若这三张牌上的数字可以是一个三角形的三条边长,那么 Alice 胜;否则 Bob 胜。若均使用最优策略,那么 Alice 是否必胜?
Solution
不想分类讨论,随便乱搞了一下。
如果前两张牌出的是 \(a,b\),那么有第三条边限制 \(c\in(\abs(a,b),a+b)\)。Bob 想赢就需要让这个区间尽量不被序列 \(a\) 相交。
把限制拆成两半,显然需要 \(c\) 尽量大或者尽量小,但合在一起就不一定了,所以乱搞把 b 里前若干最大/小的数拿出来枚举判。
正确性没有,正确率未知。
\(\\\)
Code
// STOOOOOOOOOOOOOOOOOOOOOOOOO hzt CCCCCCCCCCCCCCCCCCCCCCCORZ
#include <algorithm>
#include <iostream>
#include <numeric>
#include <vector>
using namespace std;
using LL = long long;
using PII = pair<int, int>;
constexpr int kN = 2e5 + 1;
int T, n, m, a[kN], b[kN], t[kN];
int main() {
cin.tie(0)->sync_with_stdio(0);
for (cin >> T; T--;) {
cin >> n, m = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n; i++) {
cin >> t[i];
}
sort(a + 1, a + n + 1);
sort(t + 1, t + n + 1);
for (int i = 1; i <= n; i++) {
if (i <= 20 || i >= n - 20)
b[++m] = t[i];
}
bool alice = 0;
for (int i = 1, mn, mx; i <= n && !alice; i++) {
bool bob = 0;
for (int j = 1; j <= m && !bob; j++) {
mn = abs(a[i] - b[j]), mx = a[i] + b[j];
auto l = upper_bound(a + 1, a + i, mn), r = upper_bound(a + i + 1, a + n + 1, mn);
bob |= (l == a + i || *l >= mx) && (r == a + n + 1 || *r >= mx);
}
alice |= !bob;
}
cout << (alice ? "Alice" : "Bob") << '\n';
}
return 0;
}
E BDFS
Solution
部分借鉴黄色蜜蜂的洛谷题解,读者可以将本文当作题解注解
其实这题的第一个难点就在于分析题意。
注意到这是在一个环上,编号相邻则有连边,从 \(1\) 号点开始扩展,但扩展出的点只可能是连续的一段,也只能向左或者向右扩展。这两种转移在队列里始终存在。
这样向左或者向右形成了一个填满 L 和 R 的,长度为 \(n-1\) 的操作序列。原题中加入队头相当于维持上一次运动状态,即和上一位相同;加入队尾相当于改变运动状态,和上一位不同。而一个操作序列对答案的贡献也很好算,它向一个方向扩展第 \(i\) 次便会造成 \(i\) 的贡献,那么总贡献是 \(\frac{C_L(C_L+1)}{2}+\frac{C_R(C_R+1)}{2}\),\(C_L,C_R\) 分别表示 L 和 R 操作次数。
\(C_L,C_R\) 并不能直接知道任何操作序列贡献期望有关东西,不好直接考虑所有序列的情况,需要先作一些转化。考虑把一个操作序列 \(s\) 的贡献期望拆开。\(\frac{n(n+1)}{2}\) 还可以看作为长度为 \(n\) 序列有序数对个数,类似的本题操作序列对答案的贡献期望可以变作为所有满足 \(s_i=s_j\) 的有序数对 \((i,j)\) 个数。(显然 \(i\) 和 \(j\) 可能相等)同时 \(s_i=s_j\) 可以看作为 \([i,j]\) 之中进行了偶数次运动状态改变。
令 \(p=\frac{P}{100}\),即求 \(\sum\limits_{i=0}^m [2|i] \binom{m}{i}(1-p)^ip^{m-i}\),\(m\) 为 \(i\) 与 \(j\) 内间距。
注意到后半段是一个二项式定理的形式。令 \(f(x)=[(1-p)x+p]^m\),则原式是求该函数偶次项系数之和。
小学奥数告诉我们 \(F(1)\) 系数之和,\(F(-1)\) 奇次项为负,相加除以二则得偶次项系数之和。因此原式 \(=\frac{F(1)+F(-1)}{2}=\frac{1+(2p-1)^m}{2}\)。除以二可以最后处理,暂时无需考虑。
位置数对的贡献与具体的 \(C_L,C_R\) 无关,可以代回原题。常数项不如令 \(q=2p-1\)。所有操作序列贡献和 \(=\sum\limits_{a=1}^{n-1}\sum\limits_{b=a}^{n-1}(1+q^{b-a})\)。\(1\) 可以直接计数,直接剥离掉。
此时数对的贡献和位置本身无关系,只和间隔有关系,不如枚举间隔算系数。有 \(\sum\limits_{m=0}^{n-2}(n-m-1)q^m\)。和 \(\sum\) 参数 \(m\) 无关的可以剥离。即 \((n-1)\sum\limits_{m=0}^{n-2}+\sum\limits_{m=0}^{n-2}mq^m\)。左边是一个等比数列求和乘一个系数,可以直接算。右边 \(m=0\) 对答案无贡献。
因此又化为 \(\sum\limits_{m=1}^{n-2}mq^m\)。\(m\) 的系数很烦,不好直接算,可以考虑拆成 \(\sum\limits_{i=1}^{n-2}\sum\limits_{m=i}^{n-2}q^m\) 的求和形式。此时右边又变成了一个等比数列求和的形式,再拆:\(\sum\limits_{i=1}^{n-2}\frac{q^{n-1}-q^i}{q-1}\)。
分母还是可以后期除,化成 \(\sum\limits_{i=1}^{n-2}q^{n-1}-q_i\)。\(q^{n-1}\) 与 \(\sum\) 参数无关系可以直接乘,\(q_i\) 又是一个等比数列的形式。这次真的拆完了。
总结:化形式,拆贡献,剥等比数列。
推式子题要什么 Code。