2021 NOIP 题解

21年的题有点难啊(除了T1),竟然没绿题,直接紫题+黑题。

T1 P7960 [NOIP2021] 报数


原题链接

这道题还是挺水的。

因为是多组询问,首先预处理出答案,然后 O ( 1 ) O(1) O(1)查询。

O ( l o g n ) O(log n) O(logn)时间内判断一个数是否含有7,其中 n n n是数值的范围。

用筛法对每个含有 7 7 7的数的倍数给标记,因为含有 7 7 7的数密度很小,这一步近似看成是 O ( n ) O(n) O(n)

然后倒着循环一遍,处理一下每个数的下一个被标记的数是哪个,预处理的总时间复杂度为 O ( n l o g n ) O(n log n) O(nlogn)

c o d e code code

#include <bits/stdc++.h>
using namespace std;
int read() {int x = 0, f = 1;char ch = getchar();while (ch < '0' || ch > '9') {if (ch == '-') f = -1;ch = getchar();}while (ch >= '0' && ch <= '9')x = x * 10 + ch - '0', ch = getchar();return x * f;
}
const int N = 1e7 + 5;
bool flag[N];
int ans[N];
//根据题意判断是否有7
bool check(int x) {while (x) {if (x % 10 == 7) {return true;}x /= 10;}return false;
}
int main() {for (int i = 1; i <= (N - 4); i++) {//判断是否是7的倍数if (check(i)) {for (int j = i; j <= (N - 4); j += i) {flag[j] = 1;}}}int x = 0;for (int i = (N - 4); i >= 1; i--) {if (flag[i])ans[i] = -1;else {ans[i] = x;x = i;}}int T;T=read();while (T--) {int x;x=read();printf("%d\n", ans[x]);}return 0;
}

T2 P7961 [NOIP2021] 数列


原题链接

这道题就开始没啥思路了,整的人挺无语的。

后来看了题解,感觉这道题还是可以理解的,当时比赛打暴力获得 45 p t s 45pts 45pts,还可以,比爆零好点。

先来说下暴力思路:

先不考虑序列里具体每个位置填哪些数,而是考虑每种数在序列中出现了多少次:

x i x_i xi为序列中值为 i i i的数的个数,即要满足 x 0 + x 1 + x 2 + . . . + x m = n x_0+x_1+x_2+...+x_m=n x0+x1+x2+...+xm=n

这个不定方程的一个1非负整数解,即为一种不考虑具体位置的方案,如果要求真正1的方案数,只需乘以对应的组合数即可。

具体来说,就是第一步从 n n n个位置里选 x 0 x_0 x0个,第二步是从 n − x 0 n-x_0 nx0个位置里选 x 1 x_1 x1个,以此类推,方案数就会乘以 C n x 0 C n − x 0 x 1 C n − x 0 − x 1 x 1 ⋯ C x m x m C_{n}^{x_{0}} C_{n-x_{0}}^{x_{1}} C_{n-x_{0}-x_{1}}^{x_{1}} \cdots C_{x_{m}}^{x_{m}} Cnx0Cnx0x1Cnx0x1x1Cxmxm

而这道题每种方案的贡献是 v a 1 ∗ v a 2 ∗ . . . ∗ v a n v_{a1}*v_{a2}*...*v_{an} va1va2...van,也就是还要再乘以 ∏ i = 0 m v i x i \prod_{i=0}^{m} v_{i}^{x_{i}} \text { } i=0mvixi 

这样可以首先预处理组合数以及每个 v i v_i vi的幂次,然后写一个爆搜把每个不定方程的解求出来1,最后检查 1 1 1的个数是否合法。
45 p t s 45pts 45pts

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int read() {int x = 0, f = 1;char ch = getchar();while (ch < '0' || ch > '9') {if (ch == '-') f = -1;ch = getchar();}while (ch >= '0' && ch <= '9')x = x * 10 + ch - '0', ch = getchar();return x * f;
}
const LL mod = 998244353;
const int N=40;
LL ans;
LL c[N][N];
LL a[105][40];
int n, m, K;
void dfs(int u, int x, LL now, LL s) {if (u == m + 1) {//__builtin_popcountll()函数非常好用,返回二进制中1的个数if (x == 0 && __builtin_popcountll(s) <= K){ans = (ans + now) % mod;}return;}for (int i = 0; i <= x; i++) {dfs(u + 1, x - i, now * c[x][i] % mod * a[u][i] % mod, s + ((LL)i << u));}
}
int main() {for (int i = 0; i <= 30; i++) {c[i][0] = 1;for (int j = 1; j <= i; j++){c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;}}n=read(),m=read(),K=read();for (int i = 0; i <= m; i++) {int x;x=read();a[i][0] = 1;for (int j = 1; j <= n; j++) {a[i][j] = a[i][j - 1] * x % mod;}}dfs(0, n, 1, 0);printf("%lld\n", ans);return 0;
}
接下来想想正解:

注意道在从小到大枚举 x i x_i xi的时候,可以把 S = 2 a 1 + 2 a 2 + . . . + 2 a n S=2^{a_1}+2^{a_2}+...+2^{a_n} S=2a1+2a2+...+2an分成两部分:

. . . 前面 0 ∼ 1 0\sim 1 01位中一共出现了多少个 1 1 1
. . .当前有多少个位进位道 i + 1 i+1 i+1

现在我们就能dp了,我们设 f [ u ] [ i ] [ j ] [ k ] f[u][i][j][k] f[u][i][j][k]表示考虑完 ≤ u \le u u的数的个数后,已经用了 i i i个数,进位为 j j j 0 ∼ u 0 \sim u 0u位一共有 k k k 1 1 1的总贡献。

最后 d p dp dp完将所有的贡献加起来就行了。

c o d e code code

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int INF = 0x3f3f3f3f;
const LL mod = 998244353;
const int N = 40;LL c[N][N], a[105][N], f[105][N][N][N];
// f[u][i][j][k] 考虑完<=u的数的个数后,已经用了i个数,进位为j,0~u 位一共有k个1int main() {int n, m, K;for (int i = 0; i <= 30; i++) {c[i][0] = 1;for (int j = 1; j <= i; j++) c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;}scanf("%d%d%d", &n, &m, &K);for (int i = 0; i <= m; i++) {int x;scanf("%d", &x);a[i][0] = 1;for (int j = 1; j <= n; j++) {a[i][j] = a[i][j - 1] * x % mod;}};f[0][0][0][0] = 1;for (int u = 0; u <= m; u++) {for (int i = 0; i <= n; i++) {for (int j = 0; j <= i; j++) {for (int k = 0; k <= i; k++) {for (int x = 0; x <= n - i; x++) {f[u + 1][i + x][(j + x) / 2][k + (j + x) % 2] += f[u][i][j][k] * a[u][x] % mod * c[n - i][x] % mod;f[u + 1][i + x][(j + x) / 2][k + (j + x) % 2] %= mod;}}}}}LL ans = 0;for (int j = 0; j <= n; j++) {for (int k = 0; k <= n; k++) {if (__builtin_popcount(j) + k <= K) ans += f[m + 1][n][j][k];}}printf("%lld\n", ans % mod);return 0;
}

T3 P7962 [NOIP2021] 方差


原题链接

这道题有点难,考的偏数学(好像这几道题都跟数学有关)

来看暴力思路:

首先需要知道方差公式为平方的平均数减去平均数的平方。

这题突破口是把 a i + 1 + a i − 1 − a i − > a i a_{i+1}+a_{i-1}-a_i->a_i ai+1+ai1ai>ai这个操作和差分联系起来,不然会觉得操作可以执行无限次,很难有一个最终的目标。

记差分数组 d i = a i − a i − 1 d_i=a_i-a_{i-1} di=aiai1,观察到对 a x a_x ax进行操作后,其差分仅 d x d_x dx d x + 1 d_{x+1} dx+1发生了改变,具体为:

d x = ( a x + 1 + a x − 1 − a x ) − a x − 1 = a x + 1 − a x = d x + 1 d_x = (a_{x+1} + a_{x-1} -a_x)-a_{x-1}= a_{x+1} - a_x = d_{x+1} dx=(ax+1+ax1ax)ax1=ax+1ax=dx+1
d x + 1 = a x + 1 − ( a x + 1 + a x − 1 − a x ) = a x − a x − 1 = d x d_{x+1}=a_{x+1}-(a_{x+1}+a_{x-1}-a_x)=a_x -a_{x-1} = d_x dx+1=ax+1(ax+1+ax1ax)=axax1=dx

可以发现一次操作仅仅是改变了相邻的两个差分,原数组的差分 d 1 d_1 d1是固定不变的,除此之外的 d 2 ∼ d n d_2\sim d_n d2dn显然都可以随便换位置,也就一共有 ( n − 1 ) ! (n-1)! (n1)!种可能,那么全排列一下能骗20分。

可以 s h u f f l e shuffle shuffle一些初始情况,然后每次随机交换两个数,检查一下是否方差更小,若不是则不交换,这样直到方差到极值为止。(前置知识)

这种方法可以骗到72分。

72 p t s 72pts 72pts

int a[N], d[N], n;
LL ans = INF;
LL cal() {for (int i = 1; i <= n; i++) {a[i] = a[i - 1] + d[i];}LL now = 0, s1 = 0, s2 = 0;for (int i = 1; i <= n; i++) s1 += a[i], s2 += a[i] * a[i];now = -s1 * s1 + s2 * n;return now;
}
int main() {scanf("%d", &n);for (int i = 1; i <= n; i++) scanf("%d", &a[i]), d[i] = a[i] - a[i - 1];int wc = 2000;mt19937 g(114514);uniform_int_distribution<int> u1(2, n);//生成伪随机数while (wc--) {shuffle(d + 2, d + n + 1, g);int t = 0;LL now = INF;while (t < 300) {int x = u1(g), y = u1(g);if (x != y) {swap(d[x], d[y]);LL tmp = cal();if (tmp < now) {t = 0;now = tmp;} else {swap(d[x], d[y]);t++;}}}ans = min(ans, now);}cout << ans << endl;return 0;
}
等等再想正解

阿江又讲了种办法,感觉就是 72 p t s 72pts 72pts的简单版,就是去了生成随机数。加了全排列。并且 72 p t s 72pts 72pts的代码更加细节一点。

28 p t s 28pts 28pts

#include<bits/stdc++.h> 
const int N=11000;
using namespace std;
int n,a[N],b[N];
long long ans;
inline long long getvar() {long long sm=0,x=0,x2=0;for(int i=1; i<=n; ++i) {x+=sm,x2+=sm*sm;sm+=b[i];}return n*x2-x*x;
}
int main() {scanf("%d",&n);for(int i=1; i<=n; ++i)scanf("%d",&a[i]);for(int i=1; i<n; ++i)b[i]=a[i+1]-a[i];ans=getvar();while(next_permutation(b+1,b+n))ans=min(ans,getvar());printf("%lld",ans);return 0;
}
下面来想正解:

正解的话需要知道一个性质:方差最小时,d数组一定是先递减后递增。
也就是把差分数组从小到大排序后,考虑存在一个核心不停往左右两边扩展,一个个地安排每个 d i d_i di的位置(放当前的最左边或者最右边),每次有两种选择,这样是可以DP的。

我们用 f [ i ] [ j ] f[i][j] f[i][j]表示考虑完前 i i i小的差分后,当前总和 ∑ a i = j \sum a_i=j ai=j时, ∑ a i 2 \sum a_i^2 ai2 的最小值。

·若把 d i d_i di放到当前核心的右端,记 t = ∑ k = 1 i d k t=\sum _{k=1}^i d_k t=k=1idk,新的 a i = t a_i=t ai=t,之前核心里的所有数都不会变化,转移为 f [ i ] [ j ] = f [ i − 1 ] [ j − t ] + t ∗ t f[i][j]=f[i-1][j-t]+t*t f[i][j]=f[i1][jt]+tt

·若把 d i d_i di放到当前核心的左端,这 i i i个数都加上了 d i d_i di(包括新加进去的 a i a_i ai,初始看成0),这样总和是加了 i ∗ d i i*d_i idi,转移为 f [ i ] [ j ] = f [ i − 1 ] [ j − i ∗ d i ] + 2 d i ( j − i ∗ d i ) + i ∗ d i 2 f[i][j]=f[i-1][j-i*d_i]+2d_i(j-i*d_i)+i*d_i^2 f[i][j]=f[i1][jidi]+2di(jidi)+idi2

上述DP总状态数有 n 2 a n^2a n2a个,每个状态都是 O ( 1 ) O(1) O(1)转移,空间需要用滚动数组来优化。

c o d e code code

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL INF = 1e18;
const LL mod = 1e9 + 7;
const int N = 10005;int a[N], d[N];
LL f[2][500005];int main() {int n;scanf("%d", &n);for (int i = 1; i <= n; i++) scanf("%d", &a[i]);int m = n - 1;for (int i = 1; i <= m; i++) {d[i] = a[i + 1] - a[i];}sort(d + 1, d + m + 1);int s = 0;for (int i = 1; i <= n; i++) s += d[i] * i;int now = 0, u = 0;for (int j = 0; j <= s; j++) f[u][j] = INF;f[0][0] = 0;for (int i = 1; i <= m; i++) {if (d[i] == 0) continue;u ^= 1;now += d[i];for (int j = 0; j <= s; j++) {f[u][j] = INF;int k = j - i * d[i];if (k >= 0) f[u][j] = min(f[u][j], f[u ^ 1][k] + (LL)i * d[i] * d[i] + (LL)2 * k * d[i]);k = j - now;if (k >= 0) f[u][j] = min(f[u][j], f[u ^ 1][k] + now * now);}}LL ans = INF;for (int j = 0; j <= s; j++) {if (f[u][j] < INF)ans = min(ans, n * f[u][j] - (LL)j * j);}cout << ans << endl;return 0;
}

T4 P7963 [NOIP2021] 棋局


原题链接

这道题还不是很会。。。

总结

这个NOIP2021年的题除了T1简单一点,其他的都挺难的,两道是dp,但暴力还是都有部分分的,还不少,dp真的好考思维,TMD。先做简单题,把能拿得分拿到手,在尽可能地争取高分,先易后难,循序渐进,最后的分数也不会很低。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/885571.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue3入门知识(一)

vue3简介 性能的提升 打包大小减少41%初次渲染快55%&#xff0c;更新渲染快133%内存减少54% 源码的升级 使用Proxy代替defineProperty实现响应式重写虚拟DOM的实现和Tree-Shaking 新的特性 1. Composition API&#xff08;组合API&#xff09; setupref与reactivecomput…

AI与就业:技术革命下的职业转型与挑战

内容概要 在当今时代&#xff0c;人工智能的迅猛发展正在深刻影响着我们的就业市场。这一技术革命不仅让我们看到了未来的职业转型&#xff0c;还引发了对于新兴技能需求的深思。随着AI技术的普及&#xff0c;许多传统行业面临着巨大的变革压力&#xff0c;同时也为新兴领域创…

小白初入Android_studio所遇到的坑以及怎么解决

1. 安装Android_studio 参考&#xff1a;Android Studio 安装配置教程 - Windows(详细版)-CSDN博客 Android Studio超级详细讲解下载、安装配置教程&#xff08;建议收藏&#xff09;_androidstudio-CSDN博客 想下旧版本的android_studio的地址&#xff08;仅供参考&#xf…

Uubntu下的Boost库安装及使用

一、Boost库介绍 Boost库是为C语言标准库提供扩展的一些C程序库的总称。 Boost库由Boost社区组织开发、维护。其目的是为C程序员提供免费、同行审查的、可移植的程序库。Boost库可以与C标准库共同工作&#xff0c;并且为其提供扩展功能。Boost库使用Boost License来授权使用&…

【王木头】最大似然估计、最大后验估计

目录 一、最大似然估计&#xff08;MLE&#xff09; 二、最大后验估计&#xff08;MAP&#xff09; 三、MLE 和 MAP 的本质区别 四、当先验是均匀分布时&#xff0c;MLE 和 MAP 等价 五、总结 本文理论参考王木头的视频&#xff1a; 贝叶斯解释“L1和L2正则化”&#xff…

「QT」几何数据类 之 QPointF 浮点型点类

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「QT」QT5程序设计&#x1f4da;全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasolid…

数据结构与算法——Java实现 54.力扣1008题——前序遍历构造二叉搜索树

不要谩骂以前的自己 他当时一个人站在雾里也很迷茫 ​​​​​​​ ​​​​​​​ ​​​​​​​—— 24.11.6 1008. 前序遍历构造二叉搜索树 给定一个整数数组&#xff0c;它表示BST(即 二叉搜索树 )的 先序遍历 &#xff0c;构造树并返回其根。 保证 对于给定…

【Leecode】Leecode刷题之路第46天之全排列

题目出处 46-全排列-题目出处 题目描述 个人解法 思路&#xff1a; todo代码示例&#xff1a;&#xff08;Java&#xff09; todo复杂度分析 todo官方解法 46-全排列-官方解法 预备知识 回溯法&#xff1a;一种通过探索所有可能的候选解来找出所有的解的算法。如果候选解…

势不可挡 创新引领 | 生信科技SOLIDWORKS 2025新品发布会·苏州站精彩回顾

2024年11月01日&#xff0c;由生信科技举办的SOLIDWORKS 2025新产品发布会在江苏苏州圆满落幕。现场邀请到制造业的专家学者们一同感受SOLIDWORKS 2025最新功能&#xff0c;探索制造业数字化转型之路。 在苏州站活动开场&#xff0c;达索系统专业客户事业部华东区渠道经理马腾飞…

CatLIP,加速2.7倍!采用分类损失的CLIP水准的预训练视觉编码器

CatLIP&#xff0c;加速2.7倍&#xff01;采用分类损失的CLIP水准的预训练视觉编码器 FesianXu 20241018 at Wechat Search Team 前言 传统的CLIP采用对比学习的方式进行预训练&#xff0c;通常需要汇聚多张节点的多张设备的特征向量以进行打分矩阵的计算&#xff0c;训练速度…

linux笔记(selinux)

一、概述 定义SELinux&#xff08;Security - Enhanced Linux&#xff09;是一种基于 Linux 内核的强制访问控制&#xff08;MAC&#xff09;安全机制。它为 Linux 系统提供了更细粒度的安全策略&#xff0c;增强了系统的安全性。目的主要目的是限制进程对系统资源&#xff08;…

江西省补贴性线上职业技能培训管理平台(刷课系统)

江西省补贴性线上职业技能培训管理平台(刷课系统) 目的是为了刷这个网课 此系统有两个版本一个是脚本运行&#xff0c;另外一个是可视化界面运行 可视化运行 技术栈:flask、vue3 原理: 通过分析网站接口&#xff0c;对某些接口加密的参数进行逆向破解&#xff0c;从而修改请求…

Scala 中 set 的实战应用 :图书管理系统

1. 创建书籍集合 首先&#xff0c;我们创建一个可变的书籍集合&#xff0c;用于存储图书馆中的书籍信息。在Scala中&#xff0c;mutable.Set可以用来创建一个可变的集合。 val books mutable.Set("朝花惜拾", "活着") 2. 添加书籍 我们可以使用操作符…

简单介绍一下mvvm mvc mvp以及区别、历史

MVC&#xff08;Model - View - Controller&#xff09; 因MVC架构的灵活性&#xff0c;架构图形式很多&#xff0c;仅供参考 历史&#xff1a; MVC 是最早出现的软件架构模式之一&#xff0c;其历史可以追溯到 20 世纪 70 年代&#xff0c;最初被用于 Smalltalk - 80 环境。…

“高级Java编程复习指南:深入理解并发编程、JVM优化与分布式系统架构“

我的个人主页 接下来我将方享四道由易到难的编程题&#xff0c;进入我们的JavaSE复习之旅。 1&#xff1a;大小写转换------题目链接 解题思路&#xff1a; 在ASCII码表中&#xff0c;⼤写字⺟A-Z的Ascii码值为65- 90&#xff0c;⼩写字⺟a-z的Ascii码值为97-122。每个字 ⺟…

设备租借系统(源码+文档+部署+讲解)

本文将深入解析“设备租借系统”的项目&#xff0c;探究其架构、功能以及技术栈&#xff0c;并分享获取完整源码的途径。 系统概述 本项目名称为设备租借系统&#xff0c;是对企业内部设备进行信息化管理的系统&#xff0c;可以实现设备的借用、归还、状态跟踪等功能&#xff…

使用 PageHelper 在 Spring Boot 项目中实现分页查询

目录 前言1. 项目环境配置1.1 添加 PageHelper 依赖1.2 数据库和 MyBatis 配置 2. 统一的分页响应类3. 使用 PageHelper 实现分页查询3.1 Service 层分页查询实现3.2 PageHelper 分页注意事项 4. 控制层调用示例5. 常见问题与解决方案5.1 java.util.ArrayList cannot be cast t…

【C++】C++移动语义、左值右值、左值引用右值引用、移动构造函数、std::move、移动赋值操作符

二十五、C移动语义、左值和右值、左值引用右值引用、移动构造函数、std::move、移动赋值操作符 本部分讨论一些更高级的C特性&#xff1a;C移动语义。但是讲移动语义之前我们得先了解什么左值右值、左值引用和右值引用。 1、C的左值和右值、左值引用和右值引用左值是有地址的…

【国内中间件厂商排名及四大中间件对比分析】

国内中间件厂商排名 随着新兴技术的涌入&#xff0c;一批国产中间件厂商破土而出&#xff0c;并在短时间内迅速发展&#xff0c;我国中间件市场迎来洗牌&#xff0c;根据市占率&#xff0c;当前我国中间件厂商排名依次为&#xff1a;东方通、宝兰德、中创股份、金蝶天燕、普元…

Android自启动管控

1. 自启动管控需求来源 自启动、关联启动、交叉启动、推送启动等现象的泛滥除了对个人信息保护带来隐患外&#xff0c;还会导致占用过多的系统CPU和内存资源&#xff0c;造成系统卡顿、发热、电池消耗过快&#xff1b;还可能引入一些包含“恶意代码”的进程在后台隐蔽启动&…