2026.9.28 考试总结
NOIP 2021
估:100 + 100 + [48, 100] + 0
实:100 + 100 + 100 + 0
模拟退火太强啦!
t4 太难了不补。
number
考虑直接暴力预处理所有合法的数,每次询问二分即可。时间复杂度 \(O(n \ln n)\),但是常数极小。
code
#include <bits/stdc++.h>
using namespace std;
#define int int
#define ll long long
#define AC return
#define AK return 0
#define el putchar('\n')
#define ys cout << "yes"
#define no cout << "no"
#define Ys cout << "Yes"
#define No cout << "No"
#define NO cout << "NO"
#define YS cout << "YES"
#define lowbit(i) (i & (-i))
#define ls(i) ch[i][0]
#define rs(i) ch[i][1]
#define Mod(x) (((x) % mod + mod) % mod)
#define void inline void
#define il inline
#define debug(x) cerr << #x << ' ' << x << '\n'
il char gc() { char c; while((c = getchar()) <= ' ') ; return c; }
il int rd() {
int x, f = 1; char c;
while(!isdigit(c = getchar())) if(c == '-') f = -f;
x = c ^ 48;
while(isdigit(c = getchar())) x = (x << 3) + (x << 1) + (c ^ 48);
return x * f;
}
void ACehomoxue();
signed main() {
srand(time(0));
int t = 1;
// freopen("number.in", "r", stdin);
// freopen("number.out", "w", stdout);
t = rd();
while(t--) ACehomoxue();
}
const int mod = 998244353, maxn = 1e7 + 18;
int n;
vector <int> vec;
bool has[maxn];
il bool chk(int x) {
while(x) {
if(x % 10 == 7) return true;
x /= 10;
}
return false;
}
void pre() {
if(vec.size()) AC;
for(int i = 1; i < maxn; i++) {
if(has[i] || chk(i)) {
has[i] = true;
for(int j = 2 * i; j < maxn; j += i) has[j] = true;
} else vec.push_back(i);
}
}
void ACehomoxue() {
pre();
n = rd();
auto p = lower_bound(vec.begin(), vec.end(), n);
if(*p != n) cout << -1;
else {
p++;
cout << *p;
}
el;
}
sequence
考虑直接数位 dp。设 \(dp_{i, c, j, k}\) 表示二进制第 \(i\) 位及以上,以前填了 \(c\) 个数,小面进位了 \(j\) 次,还剩最多 \(k\) 个位置能填一。答案显然为 \(dp_{0, 0, 0, k}\)。对于初始状态,即当 \(i = m + 1\),若 \(k < \operatorname{popcount}(j)\) 或 \(c \ne n\) 显然为 \(0\),否则 \(n\) 个数字随便排有 \(c!\) 的方案数。考虑转移,枚举填了 \(t\) 个 \(v_i\),那么贡献应为 \({v_i} ^ t\),且内部没有顺序应该再乘上 \(\frac 1 {t!}\),然后直接向前面的 dp 数组转移,总结一下有:
直接转移即可,时间复杂度 \(O(m n ^ 4)\)。做完了。
code
#include <bits/stdc++.h>
using namespace std;
#define int ll
#define ll long long
#define AC return
#define AK return 0
#define el putchar('\n')
#define ys cout << "yes"
#define no cout << "no"
#define Ys cout << "Yes"
#define No cout << "No"
#define NO cout << "NO"
#define YS cout << "YES"
#define lowbit(i) (i & (-i))
#define ls(i) ch[i][0]
#define rs(i) ch[i][1]
#define Mod(x) (((x) % mod + mod) % mod)
#define void inline void
#define il inline
#define debug(x) cerr << #x << ' ' << x << '\n'
il char gc() { char c; while((c = getchar()) <= ' ') ; return c; }
il int rd() {
int x, f = 1; char c;
while(!isdigit(c = getchar())) if(c == '-') f = -f;
x = c ^ 48;
while(isdigit(c = getchar())) x = (x << 3) + (x << 1) + (c ^ 48);
return x * f;
}
void ACehomoxue();
signed main() {
srand(time(0));
int t = 1;
// freopen("sequence.in", "r", stdin);
// freopen("sequence.out", "w", stdout);
// t = rd();
while(t--) ACehomoxue();
}
const int mod = 998244353, maxn = 33, maxm = 114;
int n, m, k, a[maxm];
int fact[maxm], inv[maxm];
il int ksm(int a, int k = mod - 2) { int res = 1; for(; k; k >>= 1, a = a * a % mod) if(k & 1) res = res * a % mod; return res; }
il int frac(int n, int m) { return n * fact[m - 1] % mod * inv[m] % mod; }
void pre() {
fact[0] = 1;
for(int i = 1; i < maxn; i++) fact[i] = fact[i - 1] * i % mod;
inv[maxn - 1] = ksm(fact[maxn - 1]);
for(int i = maxn - 2; i + 1; i--) inv[i] = inv[i + 1] * (i + 1) % mod;
}
int dp[maxm][maxn][maxn][maxn];
bool vis[maxm][maxn][maxn][maxn];
il int f(int i, int c, int j, int k) {
if(k < 0) AK;
if(i > m) {
if(c != n || k < 0) AK;
int cnt = __builtin_popcount(j);
if(cnt > k) AK;
return fact[c];
}
if(vis[i][c][j][k]) return dp[i][c][j][k];
vis[i][c][j][k] = true;
int &F = dp[i][c][j][k];
for(int v = 1, t = 0, tmp; c + t <= n; t++, v = v * a[i] % mod) {
tmp = f(i + 1, c + t, (j + t) / 2, k - ((j + t) & 1));
F = (F + tmp * inv[t] % mod * v) % mod;
}
return F;
}
void ACehomoxue() {
pre();
n = rd(), m = rd(), k = rd();
for(int i = 0; i <= m; i++) a[i] = rd();
cout << f(0, 0, 0, k);
el;
}
variance
提供一种容错极高的模拟退火做法,和正解等价。
直接看发现很没思路,想一想转换成差分或前缀和。转换成差分之后,我们发现操作变成了交换两个差分的值,但注意第一个数不能换。由于方差最小,那么按照定义所有数字就应该离平均值最近,显然最后答案的差分序列是单谷的。直接二进制枚举每个数在谷的左边还是右边的 \(O(2 ^ n n)\) 做法可以拿到 \(n \le 20\) 的 \(48\) 分。对于 \(n > 20\),设 \(pos_i\) 表是 \(i\) 在谷的左边还是右边,不难发现我们确定 \(pos\) 之后就可以 \(O(n)\) 确定整个数组,那么我们可以对 \(pos\) 模拟退火,每次选一个数改变其和谷的位置关系。由于这个性质特别严卡时卡到 \(0.7s\) 就可以随便过了,测了 \(1000\) 次大样例只 wa 了 \(10\) 次不到。做完了。
code
#include <bits/stdc++.h>
using namespace std;
#define int ll
#define ll long long
#define AC return
#define AK return 0
#define el putchar('\n')
#define ys cout << "yes"
#define no cout << "no"
#define Ys cout << "Yes"
#define No cout << "No"
#define NO cout << "NO"
#define YS cout << "YES"
#define lowbit(i) (i & (-i))
#define ls(i) ch[i][0]
#define rs(i) ch[i][1]
#define Mod(x) (((x) % mod + mod) % mod)
#define void inline void
#define il inline
#define debug(x) cerr << #x << ' ' << x << '\n'
il char gc() { char c; while((c = getchar()) <= ' ') ; return c; }
il int rd() {
int x, f = 1; char c;
while(!isdigit(c = getchar())) if(c == '-') f = -f;
x = c ^ 48;
while(isdigit(c = getchar())) x = (x << 3) + (x << 1) + (c ^ 48);
return x * f;
}
void ACehomoxue();
signed main() {
srand(time(0));
int t = 1;
// freopen("variance.in", "r", stdin);
// freopen("variance.out", "w", stdout);
// t = rd();
while(t--) ACehomoxue();
}
const int mod = 998244353, maxn = 1e4 + 18;
int n, a[maxn], d[maxn], ans;
vector <int> v1, v2;
il int get() {
int res = 0, sum = 0, x = 0;
while(!v1.empty()) {
x += v1.back();
res += n * x * x;
sum += x;
v1.pop_back();
}
for(int d : v2) {
x += d;
res += n * x * x;
sum += x;
}
v1.clear(), v2.clear();
return res - sum * sum;
}
bool pos[maxn];
il int calc() {
v2.push_back(d[2]);
for(int i = 3; i <= n; i++) {
if(pos[i - 3] & 1) v1.push_back(d[i]);
else v2.push_back(d[i]);
}
v1.push_back(d[1]);
return get();
}
mt19937 rng(time(0));
const double eps = 1e-8;
il double rng01() { return (rng() % 10000000) / 1e7; }
void sa() {
double t = 2e5, dt = 0.997;
v2.push_back(d[2]);
for(int i = 3; i <= n; i++) pos[i - 3] = (rng() & 1);
int now = calc();
while(t >= eps) {
int p = rng() % (n - 2);
pos[p] ^= 1;
int nxt = calc();
if(nxt < now || rng01() > exp(-t)) now = nxt;
else pos[p] ^= 1;
ans = min(ans, nxt);
t *= dt;
}
}
void ACehomoxue() {
n = rd();
for(int i = 1; i <= n; i++) a[i] = rd(), d[i] = a[i] - a[i - 1];
sort(d + 2, d + n + 1);
ans = 1e9;
if(n <= 20) {
for(int s = 0; s < (1 << (n - 2)); s++) {
v2.push_back(d[2]);
for(int i = 3; i <= n; i++) {
if((s >> (i - 3)) & 1) v1.push_back(d[i]);
else v2.push_back(d[i]);
}
v1.push_back(d[1]);
ans = min(ans, get());
}
cout << ans;
el;
} else {
while((double)clock() / CLOCKS_PER_SEC <= 0.7) sa();
cout << ans;
el;
}
}

浙公网安备 33010602011771号