Loading

「解题报告」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?题目都读不懂,感觉像个模拟,果断舍弃 真的不是因为看它是个黑题

posted @ 2023-09-21 17:31  yi_fan0305  阅读(58)  评论(0编辑  收藏  举报