2024-09-24 模拟赛总结
\(0+30+0+0=30\),挂惨了。
比赛链接:http://172.45.35.5/d/HEIGETWO/homework/66f10eb944f0ed11b057157e
或 http://yl503.yali.edu.cn/d/HEIGETWO/homework/66f10eb944f0ed11b057157e
A - 依依寺
题意:
现在有 \(a\) 个 \(0\),\(b\) 个 \(1\),\(c\) 个 \(2\),有两个人 F 和 S,F 先手,每次操作选择一个数,若所有操作累计的和 \(s\) 为 \(3\) 的倍数,那么操作的那一方输,如果某一方不能操作,那一方也输。两人轮流操作,若两人都已最优操作操作,求谁必赢。
思路:
显然先手不能先选 \(0\),如果不考虑 \(0\),那么只可能如下选择:
- \(1-1-2-1-2-\cdots\)。
- \(2-2-1-2-1-\cdots\)。
在考虑 \(0\) 的情况:选择 \(0\) 只会改变先后手关系,所以只需要考虑 \(a\) 的奇偶性。
若 \(a\) 为偶数,那么 \(0\) 就没有用,根据上面的性质,当 \(a,b\ge 2\) 时,F 一定能赢,边角情况随便讨论一下即可。
若 \(a\) 为奇数,那么 \(0\) 指挥改变先后手关系,考虑 \(a,b\) 较大且 \(|a-b|\) 较大的情况。
- 若 F 先拿元素较少的一堆,接下来就是轮流选 \(12\),S 可以取一个 \(0\),这样 S 就赢了。
- 若 F 先拿元素叫多的一堆,接下来就是轮流选 \(12\),S 肯定不会取 \(0\),否则 S 就输了,但是 F 可以选 \(0\),那么 F就赢了。
综上,这种情况的 F 必赢。边角情况在代码中给出。
代码:
#include <bits/stdc++.h>
using namespace std;
int T;
long long a, b, c;
bool C(long long a, long long b, long long c) {
if (a % 2) {
if (b == 0) {
return c && c != 1;
} else {
return b - 1 > c || c - 1 > b;
}
} else {
if (b == 0) {
return c == 1;
} else if (b == 1) {
return 1;
} else {
return c;
}
}
}
int main() {
freopen("yiyi.in", "r", stdin);
freopen("yiyi.out", "w", stdout);
ios::sync_with_stdio(0), cin.tie(0);
for (cin >> T; T; T--) {
cin >> a >> b >> c;
cout << (C(a, b, c) ? "First\n" : "Second\n");
}
return 0;
}
B - 武义寺
题意:
给定 \(n\),随机生成一个排列 \(p_n\),定义 \(val(p)\) 为 \(p\) 中的最小的 \(i\) 使得 \(i>p_i\),求 \(val(p)\) 的期望。
思路:
由于 \(p\) 是随机的所以只需要求所有 \(val(p)\) 的和在除以 \(n!\) 即可。考虑下面这个图。
从左往右第 \(i\) 列,从下往上第 \(j\) 行涂黑表示 \(p_i=j\),若 \(p\) 为排列,那么每行每列都只能有一个格子涂黑,\(val(p)\) 就是从左往右第一个涂黑格子在红色线下面的列数。
设第一个这种涂黑格子在第 \(i\) 处,那么我们就需要求出前 \(i-1\) 列都在红色格子上面的涂法。考虑枚举第 \(i\) 列的黑色格子涂在哪里,若 \(p_i=j\),那么数量就是 \((n-i+2)^{i-j-1}\times(n-i+1)^j\times(n-i)!\),\((n-i+2)^{i-j-1}\) 表示 \(j+1\) 到 \(i-1\) 所有方案的乘积,\((n-i+1)^j\) 表示 \(1\) 到 \(j\) 所有方案的乘积,\((n-i)!\) 表示 \(i+1\) 到 \(n\) 随便放。令 \(k=n+i+1\),再累加起来,推推式子:
那么总数就是 \(\displaystyle\sum_{i=1}^n (((n-i+2)^{i-1}-(n-i+1)^{i-1})\times(n-i+1)\times(n-i)!)+n+1\),要加 \(n+1\) 是因为可能不合法。
代码:
#include <bits/stdc++.h>
using namespace std;
const int kMaxN = 1e6 + 5, kM = 998244353;
int n;
long long f[kMaxN], ans;
long long fpow(long long a, long long b, long long p, long long r = 1) {
for (a %= p; b; b & 1 && (r = r * a % p), b >>= 1, a = a * a % p) {
}
return r;
}
int main() {
freopen("wuyi.in", "r", stdin);
freopen("wuyi.out", "w", stdout);
cin >> n, f[0] = 1;
for (int i = 1; i <= n; i++) {
f[i] = f[i - 1] * i % kM;
}
for (int i = 1; i <= n; i++) {
ans = (ans + i * f[n - i + 1] % kM * (fpow(n - i + 2, i - 1, kM) - fpow(n - i + 1, i - 1, kM)) % kM) % kM;
}
cout << (ans + n + 1 + kM) * fpow(f[n], kM - 2, kM) % kM;
return 0;
}
C - 依久依久
题意:
对于一个正整数 \(x\),那么 \(x\) 可以表示成多个斐波那契数之和,若这些斐波那契数互不相同且不相邻,那么这种分解是唯一的。
形式化的说,\(x=\sum_{i=1}^k fib_{a_i},a_i>a_{i-1}+1\),这种分解时唯一的,设 \(val(x)=\displaystyle\bigoplus_{i=1}^k fib_{a_i}\)。给定 \(l,r\),求 \(\displaystyle\bigoplus_{i=l}^r val(i)\)。
思路:
首先考虑用前缀和将题目转化成求 \(S(x)=\displaystyle\bigoplus_{i=1}^x val(i)\),先想一想可以如何构造,对于 \(x\),可以找到最大的小于等于 \(x\) 的斐波那契数,然后将 \(x\) 减去这个数,一直这样递归即可。这样为什么不会出现相邻的斐波那契数呢?因为如果出现相邻的斐波那契数,那么 \(x\ge fib_i+fib_{i+1}=fib_{i+2}\),那么最大的小于等于 \(x\) 的数就不是 \(fib_i\) 了。
考虑如下计算 \(S(x)\):
解释一下这个式子,先找到第一个小于等于 \(x\) 的斐波那契数,那么 \(fib_i\) 到 \(x\) 都要选择 \(fib_i\),并且要减去 \(fib_i\),所以要异或 \(S(x-fib_i)\bigoplus ((x-fib_i+1)\bmod 2)\times fib_i\),那么小于 \(fib_i\) 的数重新算即可。
由于 \(fib_i\) 和 \(2^i\) 的值域差不多,每次减去 \(fib_i\),那么时间复杂度就是 \(\log W\),\(W\) 是值域大小,加上记忆化搜索就可以过了。
代码:
#include <bits/stdc++.h>
using namespace std;
const int kMaxN = 90;
int T;
long long l, r, fib[kMaxN], ans;
map<long long, long long> mp;
long long f(long long x) {
if (mp.count(x)) {
return mp[x];
}
int p = upper_bound(fib, fib + kMaxN, x) - fib - 1;
return mp[x] = f(fib[p] - 1) ^ f(x - fib[p]) ^ ((x - fib[p] + 1) % 2 ? fib[p] : 0);
}
int main() {
freopen("yijiu.in", "r", stdin);
freopen("yijiu.out", "w", stdout);
ios::sync_with_stdio(0), cin.tie(0);
fib[0] = fib[1] = 1, mp[0] = 0, mp[1] = 1;
for (int i = 2; i < kMaxN; i++) {
fib[i] = fib[i - 1] + fib[i - 2];
}
for (cin >> T; T; T--, ans = 0) {
cin >> l >> r;
cout << (f(r) ^ f(l - 1)) << '\n';
}
return 0;
}
D - 补幺梨
题意:
有 \(n\) 种货币,面值分别为 \(a_1,a_2,\cdots,a_n\),每种货币你都有无限张,求最大的凑不出来的价格(不能找钱)。
思路:
设 \(a_0=\displaystyle\min_{i=1}^n a_i\),考虑在模 \(a_0\) 的系统下,若 \(0\le r<a_0\) 存在,那么 \(r+n\times a_0\) 也能凑成,我们用最短路求出能凑出的 \(x\bmod a_0=r\) 的最小的 \(x\),那么 \(x-a_0\) 一定没有被凑到,否则 \(x\) 就不是最小的了,所以只需要求出能到余数 \(r\) 的最小的 \(x\) 减去 \(a_0\) 在去最大值即可。
这种方法叫做同余最短路。
代码:
#include <bits/stdc++.h>
using namespace std;
const int kMaxN = 1e7 + 5, kMaxM = 1e8 + 5;
int n, m, a[kMaxN], mod;
long long dis[kMaxM], ans = -1;
bitset<kMaxM> vis;
void dijkstra(int s) {
priority_queue<pair<long long, int>> q;
for (fill(dis, dis + mod, 1e18), vis = 0, q.push({dis[s] = 0, s}); q.size(); ) {
int u = q.top().second;
q.pop();
if (vis[u]) {
continue;
}
vis[u] = 1;
for (int i = 1, v, w; i <= n; i++) {
v = (u + a[i]) % mod, w = a[i];
if (dis[v] > dis[u] + w) {
q.push({-(dis[v] = dis[u] + w), v});
}
}
}
}
int main() {
freopen("pear.in", "r", stdin);
freopen("pear.out", "w", stdout);
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
mod = *min_element(a + 1, a + 1 + n), dijkstra(0);
for (int i = 0; i < mod; i++) {
dis[i] != 1e18 && (ans = max(ans, dis[i] - mod));
}
cout << ans;
return 0;
}