20250424 模拟赛题解
20250424 模拟赛题解
三个都是原题:DMOJ coi10p4 | 洛谷 P4459 | 洛谷 P6682
A. COI '10 #4 Kamion
dp [蓝]
为了便于描述,把题意转化成:有 \(26\) 种括号,每条边上有一个左括号/右括号或者没有括号,要求 \(1 \mapsto n\) 的路径上经过的括号序列匹配,可以剩下一些左括号,求方案数。
计数题,容易想到 dp。关键在于怎么刻画状态。直观的想法是在状态中记录车上装载了哪些货物,但这显然是不可行的。我自己想时就卡在这里了。为了设计出可用的状态,必须抛弃记录整个栈的想法。先假设不存在没有括号的边,且要求经过的括号序列必须合法(即不能剩下左括号)。
要记录车上装载了哪些货物,实际上是因为陷入了一种思维定势,即只想到刻画路径中一段前缀的状态。这题中应该考虑刻画路径中间一段的状态,转移时在路径的两端拼接新边。(ABC394E 也使用了这种思想,那题要刻画的问题是回文路径)
设 \(f(s, t, i)\) 表示 \(s \mapsto t\) 有多少条长度为 \(i\) 且括号序列合法的路径。枚举点 \(u\) 进行转移,那么 \(s \mapsto t\) 的路径可以分解为 \(s \mapsto u \mapsto t\),两段都需要是合法括号序列,且两段长度之和为 \(i\)。(注意 \(u\) 可以等于 \(t\)。)但直接用 \(f\) 转移会重复贡献:例如路径 \(\tt{()()()}\) 会被贡献三次:\(\tt{()} + \tt{()()}\),\(\tt{()()} + \tt{()}\),\(\tt{()()()} + \epsilon\)。为了避免这种情况,我们规定只在括号第一次匹配的时候转移。那么 \(\tt{()()()}\) 只会贡献一次,即 \(\tt{()} + \tt{()()}\)。这需要我们记录另一个 dp 数组 \(g(s, t, i)\),表示\(s \mapsto t\) 长度为 \(i\),括号序列合法且括号序列任意一个前缀都不合法的路径数量。(即形如 (<expr>)
)。\(g(s, t, i)\) 的转移是容易的:枚举 \(s\) 的出边 \((s, u)\) 和 \(t\) 的入边 \((v, t)\),如果两条边有种类相同的左右括号,则 \(g(s, t, i) \gets g(s, t, i) + f(u, v, i - 2)\)。于是可以得到 \(f\) 的转移方程:
然后考虑原问题。首先,我们修改 \(f\) 和 \(g\) 的定义,允许在路径中任意插入空的边(但要求 \(g\) 的形式还是 (<expr>)
,最外层括号之外不能有空的边)。这不影响 \(g\) 的转移,对 \(f\),只需多考虑一种情况,即 \(s \mapsto t\) 路径上的第一条边为空,其它不变。然后我们还允许一些左括号不匹配。再设状态 \(h(s, t, i)\) 表示一些左括号可以不匹配的方案数。那么 \(s \mapsto t\) 的路径可以分为两种:
-
路径上的第一条 \((s, u)\) 边为空,或者是不匹配的左括号,方案数为 \(h(u, t, i - 1)\);
-
路径上的第一条边是匹配的左括号,方案数为
\[\sum_{1 \le j \le i} \sum_{u \in V} g(s, u, j) \cdot h(u, t, i - j) \]
直接转移即可。时间复杂度 \(O(N^{3}K^{2})\)。代码
B. [BJOI2018] 双人猜数游戏
(= LOJ2511)
数学,长链思考 [黑]
第一道自己做出的黑题,值得纪念。(虽然并没有在赛时做出来,而且想了一天)
先考虑 \(t = 2\) 的情况。
假设第一轮先问 Bob。由于 Bob 回答不知道,说明把 \(m + n\) 分解成两个 \(\ge s\) 的整数之和的方式不唯一。如果整数 \(x\) 满足把它分解为两个 \(\ge s\) 的数之和的方式不唯一,我们称 \(x\) 具有性质 \(1\)。
第二轮问 Alice。由于 Alice 回答不知道,说明把 \(mn\) 分解成两个 \(\ge s\) 的整数之积的方式不唯一。在此基础上还要考虑第一轮询问得到的信息:\(m + n\) 具有性质 \(1\)。如果把 \(mn\) 分解成两个 \(\ge s\) 的整数之积的方式不唯一,但这些方式中具有性质 \(1\) 的 \(m + n\) 唯一,那么 Alice 也可以确定 \(m\) 和 \(n\)。所以严谨的表述为:\(mn\) 分解成两个 \(\ge s\) 的整数之积,且两数之和具有性质 \(1\) 的方式不唯一。我们称这样的整数 \(mn\) 具有性质 \(2\)。
为了不把自己绕晕,拿样例 1 验证一下目前的成果:
-
第一轮:\(16\) 的分解方式有:
\[\begin{aligned} 16 &= 5 + 11 \\ &= 6 + 10 \\ &= 7 + 9 \\ &= 8 + 8 \end{aligned} \]分解方式不唯一。
-
第二轮:\(60\) 的分解方式有:
\[\begin{aligned} 60 &= 5 \times 12 \qquad (17) \\ &= 6 \times 10 \qquad (16) \end{aligned} \]括号中的数代表这种分解方式的 \(m + n\)。容易验证 \(16\) 和 \(17\) 都具有性质 \(1\)。(实际上 \(\ge 2(s + 1)\) 的数分解成两个不小于 \(s\) 的数的和的方式都不唯一)
继续推理。第三轮问 Bob,此时 Bob 回答知道。这说明把 \(m + n\) 分解成两个数的和的方式中,只有一种方式的 \(mn\) 具有性质 2。我们分别验证:
- \(55 = 5 \times 11\) 只有一种分解方式,不具有性质 \(2\)。
- \(60\) 具有性质 \(2\)。
- \(7 \times 9 = 63\) 只有一种分解方式,不具有性质 \(2\)。
- \(8 \times 8 = 64\) 只有一种分解方式,不具有性质 \(2\)。
因此 Bob 确实可以确定,唯一可能的分解方式是 \(m = 6, n = 10\)。我们称满足以下条件的整数 \(m + n\) 具有性质 \(3\):
- 具有性质 \(1\)。
- 在所有把 \(m + n\) 分解成两个 \(\ge s\) 的整数的和的方案中,\(mn\) 具有性质 \(2\) 的方案唯一。
也就是说,Bob 之所以能确定 \(m\) 和 \(n\),是因为 \(m + n\) 具有性质 \(3\)。
最后看第 \(4\) 轮询问,此时 Alice 回答知道。也就是说 Alice 排除了 \(5 \times 12\) 这种分解方式。这是因为 \(5 + 12 = 17\) 不具有性质 \(3\)。我们验证一下。把 \(17\) 分解成两个数的和的方案有:
括号中的数字代表对应方案中的 \(mn\)。如果 \(17\) 不具有性质 \(3\),则分解方案的 \(mn\) 中具有性质 \(2\) 的不唯一。分别验证是否具有性质 \(2\):
- \(60\) 具有性质 \(2\)。
- \(66\) 只有 \(6 \times 11\) 一种分解方式,不具有性质 \(2\)。
- \(70 = 5 \times 14 = 7 \times 10\) 有两种分解方式,具有性质 \(2\)。
因此 \(17\) 不具有性质 \(3\),\(16\) 是唯一可能的 \(m + n\),所以 Alice 确实可以确定 \(m\) 和 \(n\)。我们称这样的整数 \(mn\) 具有性质 \(4\):
- 具有性质 \(2\)。
- 在所有把 \(mn\) 分解成两个 \(\ge s\) 的整数的积的方案中,\(m + n\) 具有性质 \(3\) 的方案唯一。
也就是说,Alice 之所以能确定 \(m\) 和 \(n\),是因为 \(mn\) 具有性质 \(4\)。
现在我们搞清楚了逻辑,想想怎么得到答案。
可以发现性质之间有递推关系:假定答案上界为 \(w\),如果我们预处理出了 \([1, w]\) 中的整数是否具有性质 \(1 \sim k - 1\),就可以计算 \([1, w]\) 中的整数是否具有性质 \(k\)。每轮计算的时间复杂度大概是 \(O(w^{2})\),但是跑不满,而且由于是提交答案题,跑的慢一点没事。
我们猜测答案不会很大,于是设定上界 \(w\) 以后预处理每个整数是否具有性质 \(1 \sim 4\),然后按 \(m + n\) 递增且 \(m\) 递增的顺序枚举答案,如果 \(mn\) 具有性质 \(4\) 且 \(m + n\) 具有性质 \(3\),则得到了答案。
以上是先询问 Bob 的情况,但先询问 Alice 的情况可以类似地推理。
然后考虑扩展到 \(t\) 没有限定的情况。实际上上述做法的可扩展性很强:可以类似地定义性质 \(i\)(\(1 \le i \le t + 2\)),然后预处理每个整数是否具有性质 \(1 \sim t + 2\) 之后枚举答案。于是就做完了。
Code
#include<bits/stdc++.h>
using namespace std;
typedef long long i64;
constexpr int N = 200000;
// constexpr int N = 80;
int s, t;
string name;
vector<array<bool, N + 1>> p;
ostream& operator<< (ostream &os, array<bool, N + 1> &arr) {
for(int i = 1; i <= N; i++) {
os << arr[i] << " \n"[i == N];
}
return os;
}
namespace Alice {
void init() {
for(int i = s * s; i <= N; i++) {
int cnt = 0;
for(int j = s; j * j <= i; j++) {
if(i % j == 0) {
cnt++;
}
}
p[1][i] += cnt > 1;
}
// cerr << p[1];
for(int i = 2 * (s +1 ); i <= N; i++) {
int cnt = 0;
for(int j = s, k = i - j; j <= k; j++, k--) {
if(1LL * j * k > N) break;
cnt += p[1][j * k];
}
p[2][i] = cnt > 1;
}
// cerr << p[2];
for(int r = 3; r <= t + 2; r++) {
cerr << "r = " << r << '\n';
if(r & 1) {
for(int i = s * s; i <= N; i++) {
if(!p[r - 2][i]) continue;
int cnt = 0;
for(int j = s; j * j <= i; j++) {
if(i % j == 0) {
cnt += p[r - 1][j + (i / j)];
}
}
p[r][i] = r > t ? (cnt == 1) : (cnt > 1);
}
} else {
for(int i = 2 * (s + 1); i <= N; i++) {
if(!p[r - 2][i]) continue;
int cnt = 0;
for(int j = s, k = i - j; j <= k; j++, k--) {
if(1LL * j * k > N) break;
cnt += p[r - 1][j * k];
}
p[r][i] = r > t ? (cnt == 1) : (cnt > 1);
}
}
// cerr << p[r];
}
}
}
namespace Bob {
void init() {
for(int i = 2 * (s + 1); i <= N; i++) {
// i 拆分成两个 >= s 的数的和的方式不唯一 (性质 1)
p[1][i] = 1;
}
// cerr << p[1];
for(int i = s * s; i <= N; i++) {
// i 拆分成两个 >= s 的数的积的方式不唯一, 且具有性质 1 的拆分方式不唯一 (性质 2)
int cnt = 0;
for(int j = s; j * j <= i; j++) {
if(i % j == 0) {
cnt += p[1][j + (i / j)];
}
}
p[2][i] = cnt > 1;
}
// cerr << p[2];
for(int r = 3; r <= t + 2; r++) {
cerr << "r = " << r << '\n';
if(r & 1) {
for(int i = 2 * (s + 1); i <= N; i++) {
if(!p[r - 2][i]) continue;
int cnt = 0;
for(int j = s, k = i - j; j <= k; j++, k--) {
if(1LL * j * k > N) break;
cnt += p[r - 1][j * k];
}
p[r][i] = r > t ? (cnt == 1) : (cnt > 1);
}
} else {
for(int i = s * s; i <= N; i++) {
if(!p[r - 2][i]) continue;
int cnt = 0;
for(int j = s; j * j <= i; j++) {
if(i % j == 0) {
cnt += p[r - 1][j + (i / j)];
}
}
p[r][i] = r > t ? (cnt == 1) : (cnt > 1);
}
}
// cerr << p[r];
}
}
}
bool check(int m, int n) {
// cerr << "check " << m << ' ' << n << '\n';
if((name == "Alice" && !(t & 1)) || (name == "Bob" && (t & 1))) {
return m <= n && p[t + 1][m * n] && p[t + 2][m + n];
} else {
return m <= n && p[t + 1][m + n] && p[t + 2][m * n];
}
}
void solve() {
for(int sum = 2 * (s + 1); ; sum++) {
for(int m = s, n = sum - m; m <= n; m++, n--) {
if(check(m, n)) {
cout << m << ' ' << n << '\n';
return;
}
}
}
}
int main() {
cin.tie(nullptr) -> sync_with_stdio(false);
// freopen("guess23.in", "r", stdin);
// freopen("guess10.out", "w", stdout);
cin >> s >> name >> t;
p.resize(t + 3);
if(name == "Alice") {
Alice::init();
} else {
Bob::init();
}
solve();
return 0;
}
C. [BalticOI 2020 Day1] 染色
Ah-Hoc [紫]
比较厉害的 Ad-Hoc 题,完全想不到啊。先鸽了。
Code
#include<bits/stdc++.h>
using namespace std;
typedef long long i64;
bool ask(i64 x) {
cout << "? " << x << endl;
bool res;
cin >> res;
return res;
}
void answer(i64 x) {
cout << "= " << x << endl;
}
void solve() {
i64 n;
cin >> n;
vector<i64> pos;
i64 lo = 1, hi = n - 1;
while(lo <= hi) {
i64 mid = (lo + hi) >> 1;
pos.push_back(mid);
lo = mid + 1;
}
reverse(pos.begin(), pos.end());
i64 be = 1, o = 1;
for(i64 x: pos) {
be += o * x;
o *= -1;
}
ask(be);
lo = 1, hi = n - 1;
i64 ans = n;
while(lo <= hi) {
i64 mid = (lo + hi) >> 1;
i64 res = ask(be += o * mid);
o *= -1;
if(res) {
hi = mid - 1;
ans = mid;
} else {
lo = mid + 1;
}
}
answer(ans);
}
int main() {
cin.tie(nullptr) -> sync_with_stdio(false);
int t;
cin >> t;
while(t--) {
solve();
}
return 0;
}