FCOI 2^2^2 总结 & 题解
话在前面:
我没有收到任何广告费。
返回比赛链接
FCOI 2^2^2 总结
统计信息
本场比赛共
| 题目 | A | B | C | D | E | F | G |
|---|---|---|---|---|---|---|---|
| 通过率 | |||||||
| 预期通过率 | |||||||
| 得分率 |
- 通过率 = 通过人数 / 比赛有效参加人数 * 100%
- 得分率 = 所有有效参加选手在这道题的总分 / 该题满分 / 比赛有效参加人数 * 100%
奖励
奖励细则详见比赛公告。
为了保护选手隐私,我们将会采取以下方式进行颁奖:
- 对于 CQYC 类的奖励,我们将会对于每一位得奖选手私发榜单,请不要在任何公开区域发布榜单内容,本人不会承担因此造成的任何损失。
- 对于 FishC 类的奖励,我们会在 FishC 论坛上对于每一位选手私发榜单,具体请见私信内容。
- 对于其他类别的奖励,奖励将会最终榜单确定后自动颁发并作私信提醒。
在 10/18 00:00 之前,若榜单有出入,请及时联系本人作修改。过期后的更改请求不会受理。
ChatSheep 将会在 10/18 00:00 ~ 10/31 23:59 内按照最终榜单颁奖,请在此期间及时联系 ChatSheep 处理。
致歉与未来
很抱歉这场比赛我们没有考虑原 G 题过高的实现难度和代码难度,为大家带来了非常不好的参赛体验,在此磕头。我们将会保证,我们团队的任何系列比赛都不会再出类似这场 G 无聊且 gaoshai 的题目。
下一次的 FCOI 我也不知道什么时候,这可能要大量的时间来沉淀出那些高质量的题目,希望大家可以谅解(其实是没选进公开赛还不小心造了数据写了题解的题目)
如果您不想要做唐题了,您也可以关注我们的团队,我们会预计在明年年初举办我们的第二场公开赛(FCRT R2),欢迎大家支持!
FCOI 2^2^2 题解
A - 对联
因为题解不能只有一个代码,所以我还额外写了这一行文本。
:::info[点击查看代码]
#include <bits/stdc++.h>
using namespace std;
signed main() {
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
string a, b, c;
cin >> a >> b >> c;
cout << a + b + c + "," + c + b + a + "," + c + c + b + b + c + b + a + "."
+ c + b + a + "," + a + b + c + "," + b + b + c + c + a + b + c + ".";
return 0;
}
:::
B - 构造题
考虑证明答案上界,首先我们发现
注意到排列
:::info[代码]
#include <bits/stdc++.h>
using namespace std;
#define int long long
signed main() {
int n;
cin >> n;
for (int i = (n + 1) / 2, j = n; i >= 1; --i, --j) {
cout << i << " ";
if (j != (n + 1) / 2) cout << j << " ";
}
}
:::
C - 无处存储
首先发现
考虑经典差分操作,记录
然后我们会发现
:::info[代码]
#include <bits/stdc++.h>
using namespace std;
#define int long long
signed main() {
int n, q;
unordered_map<int, int> cnt;
cin >> n >> q;
while (q--) {
int x, w, tot = 0;
cin >> x >> w;
cnt[x] += w; w = 0;
while (x) {
w += cnt[x] * (++tot);
x >>= 1;
}
cout << w << "\n";
}
}
:::
可以使用值域线段树(或者 01-Trie)来做到严格
D - 关于打表
一道推式子题,通过神秘方法得知选手可能要推 1.5h(?)。
首先考虑这个
然后我们考虑每一个数位的贡献是什么,比如说第
容易发现向左拓展不会改变
所以,第
那这样答案就很明显了,对于最高位,对答案的贡献为
仿照上述把非最高位的推出来,答案就是:
这里给出 Python3 的示例代码:
:::info[示例代码]
n = int(input())
p = 998244353;
ans = 5 * (pow(10, n, p) - 1) % p * pow(10, n - 1, p) % p
for i in range(2, n + 1):
ans += i * (pow(10, n - i + 1, p) - 1) * 45 * pow(10, n - 2, p)
ans %= p
print(ans)
:::
接下来我们需要引入一个定理:
p \in P$,$a^b\equiv a^{b\bmod p-1} \pmod p
如果您不知道,您可以理解为他能处理在乘方运算中,指数较大的情况。
然后
为了简洁这里把
那前面这一坨怎么计算呢,我们考虑把这个写成递推的样子,即让
这样就能轻松
:::info[50 分]
n = int(input())
p = 998244353;
ans = 5 * (pow(10, n, p) - 1) % p * pow(10, n - 1, p) % p
ans2 = 0;
for i in range(2, n + 1):
ans2 = ans2 * 10 + i * 10;
ans2 %= p;
ans += (ans2 - (2 + n) * (n - 1) // 2) * 45 * pow(10, n - 2, p)
print(ans % p)
:::
现在我们的目标显然是要求出
以下是鄙人的推导,仅供参考。
:::info[点击展开]
设
因此:
综上,
:::
于是就可以写出这样的代码:
:::info[70 分]
n = int(input())
p = 998244353;
ans = 5 * (pow(10, n, p) - 1) % p * pow(10, n - 1, p) % p
ans2 = (19 * pow(81, p - 2, p) % p * pow(10, n, p) % p - 10 * pow(9, p - 2, p) * n - 100 * pow(81, p - 2, p)) % p;
ans += (ans2 - (2 + n) * (n - 1) // 2) * 45 * pow(10, n - 2, p)
print(ans % p)
:::
如果想要得到
这里给出 C++ 的参考实现。
:::info[代码]
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int p = 998244353;
int n, inv9 = 443664157, inv2 = 499122177;
int qpow(int a, int b) {
b = (b % (p - 1) + p - 1) % (p - 1);
int res = 1;
while (b) {
if (b & 1) res = res * a % p;
b >>= 1;
a = a * a % p;
}
return res;
}
signed main() {
int ve = 0, v = 0;
char ch = getchar();
while (ch >= '0' && ch <= '9') {
ve = (ve * 10 + ch - '0') % (p - 1);
v = (v * 10 + ch - '0') % (p);
ch = getchar();
}
int ans = 5ll * (qpow(10, ve) - 1) % p * qpow(10, ve - 1) % p;
int ans2 = (19ll * inv9 % p * inv9 % p * qpow(10, ve) % p - 10 * inv9 * v % p - 100 * inv9 % p * inv9 % p);
ans += (ans2 - (2 + v) * (v - 1) % p * inv2 % p) * 45 % p * qpow(10, ve - 2) % p;
cout << (ans % p + p) % p << "\n";
return 0;
}
:::
E - 听不清楚
看上去很骇人,其实有点板子。
首先要理解
接下来,我们考虑把所有
没有两个连续的 1 自然可以想到斐波那契编码,具体的,我们把这个序列
- 找到不大于
a 的最大斐波那契数f_n 。 - 将
a 减去f_n ,并将编码后的序列的第n 位标记为1 。
解码是简单的,直接把对应位置的斐波那契数累加即可。
这样做显然是对的,编码后的序列绝对不会出现两个
不难发现
:::info[代码]
#include <bits/stdc++.h>
using namespace std;
#define ul unsigned int
#define lll unsigned __int128
const int N = 128, M = 185;
int init = 0;
lll f[M+2];
void get_f() {
init = 1; f[0] = 1; f[1] = 2;
for (int i = 2; i <= M; i++) {
f[i] = f[i - 1] + f[i - 2];
}
}
vector<ul> encode(const vector<ul>& a) {
if (!init) get_f();
vector<lll> num(32);
for (int j = 0; j < 32; j++) for (int k = 0; k < N; k++)
if ((a[k] >> j) & 1u) num[j] += ((lll)(1) << k);
vector<ul> b(M, 0);
for (int j = 0; j < 32; j++) for (int i = 0; i < M; i++)
if (num[j] >= f[M - i - 1]) {
b[i] |= (1u << j);
num[j] -= f[M - i - 1];
}
return b;
}
vector<ul> decode(const vector<ul>& b) {
if (!init) get_f();
vector<lll> num(32);
for (int j = 0; j < 32; ++j) for (int i = 0; i < M; ++i)
if ((b[i] >> j) & 1u) num[j] += f[M - i - 1];
vector<ul> a(N, 0);
for (int k = 0; k < N; ++k) for (int j = 0; j < 32; j++)
if ((num[j] >> k) & 1) a[k] |= (1u << j);
return a;
}
// 省去 main 部分
:::
F - 列车号
很板的题以至于可以一句话题解。
我们考虑这个
如果你有耐心推式子求通项可以做到 并且很懒所以没卡。
:::info[代码]
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int p = 998244353;
struct Mat {
int m[4][4];
Mat operator * (const Mat &mt) const {
Mat d = {};
for (int k = 1; k <= 3; ++k)
for (int j = 1; j <= 3; ++j)
if (mt.m[k][j])
for (int i = 1; i <= 3; ++i)
if (m[i][k])
(d.m[i][j] += m[i][k] * mt.m[k][j] % p) %= p;
return d;
}
};
int n, a, b, c, d;
void solve() {
scanf("%lld%lld%lld%lld%lld", &n, &a, &b, &c, &d);
int lastf = 1;
for (int l = 2; l <= n; ++l) {
int val = d / l, r = min(n, val ? d / val : 1ll << 60);
val %= p;
Mat res = {}, tmp = {};
res.m[1][1] = res.m[2][2] = res.m[3][3] = 1;
tmp.m[1][1] = a; tmp.m[1][2] = (b - c * val % p) % p;
tmp.m[2][2] = tmp.m[2][3] = tmp.m[3][3] = 1;
tmp.m[1][3] = c * (d % p) % p;
int pows = r - l + 1;
while (pows) {
if (pows & 1) res = tmp * res;
tmp = tmp * tmp;
pows >>= 1;
}
lastf = ((lastf * res.m[1][1]) % p + (l % p * res.m[1][2]) % p + res.m[1][3]) % p;
l = r;
}
printf("%lld\n", (lastf + p) % p);
}
signed main() {
int tc; scanf("%lld", &tc);
while (tc--) solve();
return 0;
}
:::
G - 数位 DP
Sub 2
不妨直接枚举
| 位置 | |||||||
|---|---|---|---|---|---|---|---|
| 1 | |||||||
| 2 | |||||||
| 3 | |||||||
| 4 | |||||||
| 5 | |||||||
| 6 |
观察上表,我们分讨出了一下几种情况:
这时候,对于情况
同理可推情况二,前面部分一定不能大于原串,后面部分随便填写,同理得到位置 3 的方案数为
而情况三相当于就是把中间扣掉的这个数字。
特别的,当
于是就显然有了
Sub 4
前缀和优化即可,对于情况三无疑就是类似于哈希的思想,记录前缀后缀和
被优化至
Sub 5
瓶颈就在于如何判断当前的位置相对于原串是大还是小的关系了。
其实上,这个问题的本质就是问
因此就不难设计二分哈希做法,期望
Sub 6
其实算法四所说的就是 Z 函数,直接
std 常数一坨,因为是远古题所以懒得改了。
:::info[代码]
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define N 3000000
int n, m, pre[N+5], suf[N+5], pw10[N+5], z[(N+5) << 1];
const int p = 998244353;
char s[N+5], t[N+5];
void zfunc(string s, string t) {
memset(z, 0, sizeof(z));
string ts = t + s;
int len = ts.size(), l = 0;
for (int i = 1; i < len; ++i) {
if (l + z[l] > i) z[i] = min(z[i - l], l + z[l] - i);
while (i + z[i] < len && ts[z[i]] == ts[i + z[i]]) z[i]++;
if (i + z[i] > l + z[l]) l=i;
}
}
int count_number(string s, string t) {
zfunc(s, t);
n = s.size(); m = t.size();
s = " " + s;
t = " " + t;
for (int i = 1; i <= n; ++i) {
pre[i] = (pre[i - 1] * 10 + (s[i] - '0')) % p;
}
suf[n + 1] = 0;
for (int i = n; i >= 1; --i) {
suf[i] = (suf[i + 1] + pw10[n - i] * (s[i] - '0')) % p;
}
int iszero = (t[1] == '0' && m == 1);
int ans = 0;
for (int i = 1 + iszero; i <= n - m + 1; ++i) {
int l = min(z[i + m - 1], m);
if (l == m) {
(ans += ((pre[i - 1] - iszero) * pw10[n - (i + m - 1)]) + suf[i + m] + 1) %= p;
} else {
(ans += (pre[i - 1] + (s[i + l] > t[l + 1]) - iszero) * pw10[n - (i + m - 1)]) %= p;
assert(s[i + l] != t[l + 1]);
}
}
return ans + iszero;
}
signed main() {
ios::sync_with_stdio(0);
int tc; cin >> tc;
pw10[0] = 1;
for (int i = 1; i <= N; ++i) {
pw10[i] = pw10[i - 1] * 10 % p;
}
while (tc--) {
string n, m;
cin >> n >> m;
cout << count_number(n, m) << "\n";
}
}
:::
G - Parity(原本被换掉的题)
:::info[题解]
考虑暴力怎么做,我们把这个
然后直接把这个塞进矩阵里面,所以就是
时间复杂度
具体的,我们发现这个矩阵的左半部分几乎都是零,而对于左上角来说,每行每列都只有一个
:::info[代码]
#include <bits/stdc++.h>
using namespace std;
#define int long long
int n, q;
char s[50004];
const int p = 2000003;
struct Mat {
int mt[32][32];
inline Mat operator * (const Mat &m) const {
Mat res = {};
int f2[16] = {};
for (int i = 0; i < 16; ++i) {
for (int j = 0; j < 16; ++j) {
if (!m.mt[i][j]) continue;
f2[i] = j;
break;
}
}
for (int i = 0; i < 16; ++i) {
for (int j = 0; j < 16; ++j) {
if (!mt[i][j]) continue;
res.mt[i][f2[j]] += 1;
for (int k = 16; k < 32; ++k) {
res.mt[i][k] += m.mt[j][k];
}
break;
}
}
for (int i = 0; i < 32; ++i) {
for (int k = 16; k < 32; ++k) {
for (int j = 16; j < 32; ++j) {
res.mt[i][j] += mt[i][k] * m.mt[k][j];
}
}
}
for (int i = 0; i < 32; ++i) {
for (int j = 16; j < 32; ++j) {
res.mt[i][j] %= p;
}
}
return res;
}
};
const int B = 1000;
Mat ns[10][B+2], I;
inline Mat qpow(Mat a, int b) {
Mat res = I;
while (b) {
if (b & 1) res = a * res;
b >>= 1;
a = a * a;
}
return res;
}
struct SGT {
int tag[32769];
Mat res[32769];
#define ls(p) (p << 1)
#define rs(p) (p << 1 | 1)
inline void push_up(int i) {
res[i] = res[ls(i)] * res[rs(i)];
}
inline void build(char *s, int i, int l, int r) {
if (l == r) {
res[i] = ns[s[l] - '0'][1];
return;
}
int mid = (l + r) / 2;
build(s, ls(i), l, mid);
build(s, rs(i), mid + 1, r);
push_up(i);
}
inline void settag(int i, int l, int r, int v) {
if (r - l + 1 <= B) res[i] = ns[v][r - l + 1];
else res[i] = qpow(ns[v][1], r - l + 1);
tag[i] = v;
}
inline void push_down(int i, int l, int r) {
if (tag[i] == -1) return;
int mid = (l + r) / 2;
settag(ls(i), l, mid, tag[i]);
settag(rs(i), mid + 1, r, tag[i]);
tag[i] = -1;
}
inline void update(int i, int l, int r, int ql, int qr, int v) {
if (l > qr || r < ql) return;
if (l >= ql && r <= qr) return settag(i, l, r, v);
int mid = (l + r) / 2; push_down(i, l, r);
update(ls(i), l, mid, ql, qr, v);
update(rs(i), mid + 1, r, ql, qr, v);
push_up(i);
}
inline void query(int i, int l, int r, int ql, int qr, Mat &fi) {
if (l > qr || r < ql) return;
if (l >= ql && r <= qr) return fi = fi * res[i], void();
int mid = (l + r) / 2; push_down(i, l, r);
query(ls(i), l, mid, ql, qr, fi);
query(rs(i), mid + 1, r, ql, qr, fi);
}
} t;
signed main() {
ios::sync_with_stdio(0); cin.tie(0);
int tc; cin >> tc >> n >> q >> (s + 1);
memset(t.tag, 255, sizeof(t.tag));
for (int i = 0; i < 10; ++i) {
for (int x = 0; x < 16; ++x) {
for (int l = 0; l < 2; ++l) {
for (int j = 0; j <= max(i, l * 9); ++j) {
ns[i][1].mt[x + 16 * l][(j ^ x) + 16 * (l || (j != i))]++;
}
}
}
for (int t = 2; t <= B; ++t) ns[i][t] = ns[i][t - 1] * ns[i][1];
}
for (int i = 0; i < 32; ++i) I.mt[i][i] = 1;
t.build(s, 1, 1, n);
while (q--) {
int o, x, y, r;
cin >> o >> x >> y >> r;
if (o == 1) {
Mat fi = I;
t.query(1, 1, n, x, y, fi);
fi = qpow(fi, r);
int res = 0;
for (int i = 0; i < 16; ++i) {
res += i * i * (fi.mt[0][i]);
res += i * i * (fi.mt[0][16 + i]);
}
cout << res % p << "\n";
} else {
t.update(1, 1, n, x, y, r);
}
}
return 0;
}
:::
题目总结
有强烈的主观意见,仅供参考。
| 题目 | 难度 |
算法标签 | 思考难度 / 实现难度* |
|---|---|---|---|
| A - 对联 | 红- | 模拟 | 800 / 800 |
| B - 构造题 | 橙 | 构造,贪心 | 1100 / 800 |
| C - 无处存储 | 黄- | 哈希,树 | 1300 / 900 |
| D - 关于打表 | 绿 | 数学 | 1900 / 1200 |
| E - 听不清楚 | 蓝 | 构造 | 2600 / 1700 |
| F - 列车号 | 蓝 | 矩阵,数论分块 | 1800 / 2200 |
| G - 数位 dp | 蓝+ | 字符串,前缀和 | 2300 / 2200 |
| G2 - Parity | 紫- | 矩阵,线段树,数位 DP | 1600 / 2900 |
所有题目均被加入了 fishc 竞赛团副团,可以在赛后查看补题。