「解题报告」NOIP 2021
总分:100 + 20 + 12 + 0 = 132。
[NOIP2021] 报数
题目描述
报数游戏是一个广为流传的休闲小游戏。参加游戏的每个人要按一定顺序轮流报数,但如果下一个报的数是 \(7\) 的倍数,或十进制表示中含有数字 \(7\),就必须跳过这个数,否则就输掉了游戏。
在一个风和日丽的下午,刚刚结束 SPC20nn 比赛的小 r 和小 z 闲得无聊玩起了这个报数游戏。但在只有两个人玩的情况下计算起来还是比较容易的,因此他们玩了很久也没分出胜负。此时小 z 灵光一闪,决定把这个游戏加强:任何一个十进制中含有数字 \(7\) 的数,它的所有倍数都不能报出来!
形式化地,设 \(p(x)\) 表示 \(x\) 的十进制表示中是否含有数字 \(7\),若含有则 \(p(x) = 1\),否则 \(p(x) = 0\)。则一个正整数 \(x\) 不能被报出,当且仅当存在正整数 \(y\) 和 \(z\) ,使得 \(x = yz\) 且 \(p(y) = 1\)。
例如,如果小 r 报出了 \(6\) ,由于 \(7\) 不能报,所以小 z 下一个需要报 \(8\);如果小 r 报出了 \(33\),则由于 \(34 = 17 \times 2\),\(35 = 7 \times 5\) 都不能报,小 z 下一个需要报出 \(36\) ;如果小 r 报出了 \(69\),由于 \(70 \sim 79\) 的数都含有 \(7\),小 z 下一个需要报出 \(80\) 才行。
现在小 r 的上一个数报出了 \(x\),小 z 想快速算出他下一个数要报多少,不过他很快就发现这个游戏可比原版的游戏难算多了,于是他需要你的帮助。当然,如果小 r 报出的 x 本身是不能报出的,你也要快速反应过来小 r 输了才行。
由于小 r 和小 z 玩了很长时间游戏,你也需要回答小 z 的很多个问题。
输入格式
第一行,一个正整数 \(T\) 表示小 z 询问的数量。
接下来 \(T\) 行,每行一个正整数 \(x\),表示这一次小 r 报出的数。
输出格式
输出共 \(T\) 行,每行一个整数,如果小 r 这一次报出的数是不能报出的,输出 \(-1\),否则输出小 z 下一次报出的数是多少。
样例 #1
样例输入 #1
4
6
33
69
300
样例输出 #1
8
36
80
-1
样例 #2
样例输入 #2
5
90
99
106
114
169
样例输出 #2
92
100
109
-1
180
样例 #3
样例输入 #3
见附件中的 number/number3.in
样例输出 #3
见附件中的 number/number3.ans
样例 #4
样例输入 #4
见附件中的 number/number4.in
样例输出 #4
见附件中的 number/number4.ans
提示
【样例解释 #1】
这一组样例的前 \(3\) 次询问在题目描述中已有解释。
对于第 \(4\) 次询问,由于 \(300 = 75 \times 4\),而 \(75\) 中含有 \(7\) ,所以小 r 直接输掉了游戏。
【数据范围】
对于 \(10\%\) 的数据,\(T \leq 10\),\(x \leq 100\)。
对于 \(30\%\) 的数据,\(T \leq 100\),\(x \leq 1000\)。
对于 \(50\%\) 的数据,\(T \leq 1000\),\(x \leq 10000\)。
对于 \(70\%\) 的数据,\(T \leq 10000\),\(x \leq 2 \times {10}^5\)。
对于 \(100\%\) 的数据,\(1 \le T \leq 2 \times {10}^5\),\(1 \le x \leq {10}^7\)。
想了很长时间,曾试着找过规律 当然最后以失败告终,然后只好写了个 筛法 + 链表,没想到竟然过了。 = =
// The code was written by yifan, and yifan is neutral!!!
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define bug puts("NOIP rp ++!");
#define rep(i, a, b, c) for (int i = (a); i <= (b); i += (c))
#define per(i, a, b, c) for (int i = (a); i >= (b); i -= (c))
template<typename T>
inline T read() {
T x = 0;
bool fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
const int N = 15000005;
int T;
ll n;
int val[N], nxt[N];
bool yes[N];
bool check(int i) { // 判断是否有 7
int tmp = i;
while (tmp) {
if (tmp % 10 == 7) {
return true ;
}
tmp /= 10;
}
return false ;
}
void make_prime(ll x) {
int R = 0;
rep (i, 7, x, 1) { // 找出所有含 7 的数字
if (!yes[i] && check(i)) {
yes[i] = 1;
val[++ R] = i;
}
}
int lim = x / 7 + 1;
rep (i, 2, lim, 1) {
rep (j, 1, R, 1) {
if (1ll * i * val[j] > x) break ;
yes[i * val[j]] = 1;
if (i % val[j] == 0) break ; // 并不知道这个有什么用……后来发现没有这一行照样过 = =
}
}
int las = 0;
rep (i, 1, x, 1) {
if (!yes[i]) { // 链表部分
nxt[las] = i;
las = i;
}
}
}
void solve() {
n = read<ll>();
if (yes[n]) {
puts("-1");
return ;
}
if (nxt[n]) {
cout << nxt[n] << '\n';
} else { // 保险情况,理论上不会进入这块
++ n;
while (yes[n]) {
++ n;
}
cout << n << '\n';
}
}
int main() {
T = read<int>();
if (T <= 10000) { // 这是当时为了防止筛法的时间复杂度错误导致全T而设计的
make_prime(250000);
} else {
make_prime(10005000);
}
while (T --) {
solve();
}
return 0;
}
[NOIP2021] 数列
题目描述
给定整数 \(n, m, k\),和一个长度为 \(m + 1\) 的正整数数组 \(v_0, v_1, \ldots, v_m\)。
对于一个长度为 \(n\),下标从 \(1\) 开始且每个元素均不超过 \(m\) 的非负整数序列 \(\{a_i\}\),我们定义它的权值为 \(v_{a_1} \times v_{a_2} \times \cdots \times v_{a_n}\)。
当这样的序列 \(\{a_i\}\) 满足整数 \(S = 2^{a_1} + 2^{a_2} + \cdots + 2^{a_n}\) 的二进制表示中 \(1\) 的个数不超过 \(k\) 时,我们认为 \(\{a_i\}\) 是一个合法序列。
计算所有合法序列 \(\{a_i\}\) 的权值和对 \(998244353\) 取模的结果。
输入格式
输入第一行是三个整数 \(n, m, k\)。
第二行 \(m + 1\) 个整数,分别是 \(v_0, v_1, \ldots, v_m\)。
输出格式
仅一行一个整数,表示所有合法序列的权值和对 \(998244353\) 取模的结果。
样例 #1
样例输入 #1
5 1 1
2 1
样例输出 #1
40
样例 #2
样例输入 #2
见附件中的 sequence/sequence2.in
样例输出 #2
见附件中的 sequence/sequence2.ans
提示
【样例解释 #1】
由于 \(k = 1\),而且由 \(n \leq S \leq n \times 2^m\) 知道 \(5 \leq S \leq 10\),合法的 \(S\) 只有一种可能:\(S = 8\),这要求 \(a\) 中必须有 \(2\) 个 \(0\) 和 \(3\) 个 \(1\),于是有 \(\binom{5}{2} = 10\) 种可能的序列,每种序列的贡献都是 \(v_0^2 v_1^3 = 4\),权值和为 \(10 \times 4 = 40\)。
【数据范围】
对所有测试点保证 \(1 \leq k \leq n \leq 30\),\(0 \leq m \leq 100\),\(1 \leq v_i < 998244353\)。
测试点 | \(n\) | \(k\) | \(m\) |
---|---|---|---|
\(1 \sim 4\) | \(=8\) | \(\leq n\) | \(=9\) |
\(5 \sim 7\) | \(=30\) | \(\leq n\) | \(=7\) |
\(8 \sim 10\) | \(=30\) | \(\leq n\) | \(=12\) |
\(11 \sim 13\) | \(=30\) | \(=1\) | \(=100\) |
\(14 \sim 15\) | \(=5\) | \(\leq n\) | \(=50\) |
\(16\) | \(=15\) | \(\leq n\) | \(=100\) |
\(17 \sim 18\) | \(=30\) | \(\leq n\) | \(=30\) |
\(19 \sim 20\) | \(=30\) | \(\leq n\) | \(=100\) |
对着 \(20\) 分去的,写了个 meet in the middle,成功过掉 \(20\) 分。
// The code was written by yifan, and yifan is neutral!!!
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define bug puts("NOIP rp ++!");
#define rep(i, a, b, c) for (int i = (a); i <= (b); i += (c))
#define per(i, a, b, c) for (int i = (a); i >= (b); i -= (c))
template<typename T>
inline T read() {
T x = 0;
bool fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
const int N = 110;
const int M = 1e6 + 5;
const int mod = 998244353;
int n, m, k;
ll ans;
ll v[N], a[N], val1[M], val2[M];
set<ll> g1, g2;
void work(int l, int r) {
ll sum = 0, Val = 1;
rep (i, l, r, 1) {
sum = (sum + (1ll << a[i])) % mod;
Val = (Val * v[a[i]]) % mod;
}
val1[sum] = (val1[sum] + Val) % mod;
g1.emplace(sum);
}
void Dfs(int u, int beg, int las) {
if (u > las) {
work(beg, las);
return ;
}
rep (i, 0, m, 1) {
a[u] = i;
Dfs(u + 1, beg, las);
}
}
void dfs(int u, int beg, int las) {
if (u > las) {
ll sum = 0, Val = 1;
rep (i, beg, las, 1) {
sum += (1ll << a[i]);
Val = (Val * v[a[i]]) % mod;
}
val2[sum] = (val2[sum] + Val) % mod;
g2.emplace(sum);
return ;
}
rep (i, 0, m, 1) {
a[u] = i;
dfs(u + 1, beg, las);
}
}
#define lowbit(x) (x & (-x))
int check(ll num) {
int cnt = 0;
while (num) {
++ cnt;
num -= lowbit(num);
}
return (cnt <= k);
}
#undef lowbit
int main() {
n = read<int>(), m = read<int>(), k = read<int>();
rep (i, 0, m, 1) {
v[i] = read<ll>();
}
Dfs(1, 1, n / 2);
dfs(n / 2 + 1, n / 2 + 1, n);
for (ll v1 : g1) {
for (ll v2 : g2) {
if (check(v1 + v2)) {
ans = (ans + val1[v1] * val2[v2] % mod) % mod;
}
}
}
cout << ans << '\n';
return 0;
}
正解是计数 DP,考场上我肯定退不出来呀……
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
template<typename T>
inline T read() {
T x = 0;
int fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
const int N = 35;
const int M = 105;
const int mod = 998244353;
int n, m, k;
ll ans;
int v[M], dp[N][N][N][N][M], f[N][N][N][M];
int fac[N], inv[N], powv[M][N];
int popcnt(ll x) {
int res = 0;
while (x) {
x -= (x & (-x));
++ res;
}
return res;
}
ll qpow(ll x, ll y) {
ll ans = 1;
while (y) {
if (y & 1) {
ans = ans * x % mod;
}
y >>= 1;
x = x * x % mod;
}
return ans;
}
void init() {
fac[0] = 1;
for (int i = 1; i <= n; ++ i) {
fac[i] = 1ll * fac[i - 1] * i % mod;
}
inv[n] = qpow(fac[n], mod - 2);
for (int i = n - 1; i >= 0; -- i) {
inv[i] = 1ll * inv[i + 1] * (i + 1) % mod;
}
}
inline ll C(int n, int m) {
return 1ll * fac[n] * inv[m] % mod * inv[n - m] % mod;
}
int main() {
n = read<int>(), m = read<int>(), k = read<int>();
init();
for (int i = 0; i <= m; ++ i) {
v[i] = read<ll>();
powv[i][0] = 1;
for (int j = 1; j <= n; ++ j) {
powv[i][j] = 1ll * powv[i][j - 1] * v[i] % mod;
}
}
f[0][0][0][0] = 1;
for (int p = 0; p <= m; ++ p) {
for (int i = 0; i <= n; ++ i) {
for (int j = 0; j <= n - i; ++ j) {
for (int h = 0; h <= n; ++ h) {
for (int num = 0; num <= k; ++ num) {
if (num - (i + h) % 2 < 0) continue;
dp[i][j][h][num][p] = 1ll * f[j][h][num - (i + h) % 2][p] * powv[p][i] % mod * C(n - j, i) % mod;
f[i + j][(i + h) / 2][num][p + 1] += dp[i][j][h][num][p];
f[i + j][(i + h) / 2][num][p + 1] %= mod;
}
}
}
}
}
int ans = 0;
for (int i = 0; i <= n; ++ i) {
for (int h = 0; h <= n; ++ h) {
for (int num = 0; num <= k; ++ num) {
if (num + popcnt((i + h) / 2) <= k) {
ans = (ans + dp[i][n - i][h][num][m]) % mod;
}
}
}
}
cout << ans << '\n';
return 0;
}
[NOIP2021] 方差
题目描述
给定长度为 \(n\) 的非严格递增正整数数列 \(1 \le a_1 \le a_2 \le \cdots \le a_n\)。每次可以进行的操作是:任意选择一个正整数 \(1 < i < n\),将 \(a_i\) 变为 \(a_{i - 1} + a_{i + 1} - a_i\)。求在若干次操作之后,该数列的方差最小值是多少。请输出最小值乘以 \(n^2\) 的结果。
其中方差的定义为:数列中每个数与平均值的差的平方的平均值。更形式化地说,方差的定义为 \(D = \frac{1}{n} \sum_{i = 1}^{n} {(a_i - \bar a)}^2\),其中 \(\bar a = \frac{1}{n} \sum_{i = 1}^{n} a_i\)。
输入格式
输入的第一行包含一个正整数 \(n\),保证 \(n \le {10}^4\)。
输入的第二行有 \(n\) 个正整数,其中第 \(i\) 个数字表示 \(a_i\) 的值。数据保证 \(1 \le a_1 \le a_2 \le \cdots \le a_n\)。
输出格式
输出仅一行,包含一个非负整数,表示你所求的方差的最小值的 \(n^2\) 倍。
样例 #1
样例输入 #1
4
1 2 4 6
样例输出 #1
52
样例 #2
样例输入 #2
见附件中的 variance/variance2.in
样例输出 #2
见附件中的 variance/variance2.ans
样例 #3
样例输入 #3
见附件中的 variance/variance3.in
样例输出 #3
见附件中的 variance/variance3.ans
样例 #4
样例输入 #4
见附件中的 variance/variance4.in
样例输出 #4
见附件中的 variance/variance4.ans
提示
【样例解释 #1】
对于 \((a_1, a_2, a_3, a_4) = (1, 2, 4, 6)\),第一次操作得到的数列有 \((1, 3, 4, 6)\),第二次操作得到的新的数列有 \((1, 3, 5, 6)\)。之后无法得到新的数列。
对于 \((a_1, a_2, a_3, a_4) = (1, 2, 4, 6)\),平均值为 \(\frac{13}{4}\),方差为 \(\frac{1}{4}({(1 - \frac{13}{4})}^2 + {(2 - \frac{13}{4})}^2 + {(4 - \frac{13}{4})}^2 + {(6 - \frac{13}{4})}^2) = \frac{59}{16}\)。
对于 \((a_1, a_2, a_3, a_4) = (1, 3, 4, 6)\),平均值为 \(\frac{7}{2}\),方差为 \(\frac{1}{4} ({(1 - \frac{7}{2})}^2 + {(3 - \frac{7}{2})}^2 + {(4 - \frac{7}{2})}^2 + {(6 - \frac{7}{2})}^2) = \frac{13}{4}\)。
对于 \((a_1, a_2, a_3, a_4) = (1, 3, 5, 6)\),平均值为 \(\frac{15}{4}\),方差为 \(\frac{1}{4} ({(1 - \frac{15}{4})}^2 + {(3 - \frac{15}{4})}^2 + {(5 - \frac{15}{4})}^2 + {(6 - \frac{15}{4})}^2) = \frac{59}{16}\)。
【数据范围】
测试点编号 | \(n \le\) | \(a_i \le\) |
---|---|---|
\(1 \sim 3\) | \(4\) | \(10\) |
\(4 \sim 5\) | \(10\) | \(40\) |
\(6 \sim 8\) | \(15\) | \(20\) |
\(9 \sim 12\) | \(20\) | \(300\) |
\(13 \sim 15\) | \(50\) | \(70\) |
\(16 \sim 18\) | \(100\) | \(40\) |
\(19 \sim 22\) | \(400\) | \(600\) |
\(23 \sim 25\) | \({10}^4\) | \(50\) |
对于所有的数据,保证 \(1 \le n \le {10}^4\),\(1 \le a_i \le 600\)。
虽然我不会这个题,题目也没大看明白,但是我看懂了样例!所以就奔着 \(n = 4\) 的部分分去了,最后成功拿下。
// The code was written by yifan, and yifan is neutral!!!
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define bug puts("NOIP rp ++!");
#define rep(i, a, b, c) for (int i = (a); i <= (b); i += (c))
#define per(i, a, b, c) for (int i = (a); i >= (b); i -= (c))
template<typename T>
inline T read() {
T x = 0;
bool fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
using db = double;
const int N = 1e4 + 5;
int n;
db minn = 1e9;
int a[4][N];
void work(int k) {
db sum = 0, aver;
rep (i, 1, n, 1) {
sum += a[k][i];
}
aver = sum / 4;
db ans = 0;
rep (i, 1, n, 1) {
ans += (a[k][i] - aver) * (a[k][i] - aver);
}
minn = min(minn, ans * n);
}
int main() {
n = read<int>();
rep (i, 1, n, 1) {
a[0][i] = read<int>();
}
// 暴力处理出 n = 4 的三种情况(用最笨的办法,不愧是我……)
a[2][1] = a[1][1] = a[0][1];
a[2][4] = a[1][4] = a[0][4];
a[1][2] = a[1][1] + a[0][3] - a[0][2];
a[1][3] = a[1][2] + a[1][4] - a[0][3];
a[2][3] = a[0][2] + a[2][4] - a[0][3];
a[2][2] = a[2][1] + a[2][3] - a[0][2];
rep (i, 0, 2, 1) {
work(i);
}
cout << minn << '\n';
return 0;
}
正解?差分推式子或模拟退火……
就要这 \(12\) 分了,以后有时间再补。
T4?题目都读不懂,感觉像个模拟,果断舍弃 真的不是因为看它是个黑题