NOIP2023模拟5联测26 题解
NOIP2023模拟5联测26 题解
感觉我这场的官方题解写的是真的挺好的,所以我只能作少量补充。你可以直接去看官方题解,如果你想的话。
T1 x
题解
没啥可说的。
的时候,我们需要比较 与 的大小。
稍微变一变形,就是比较 与 的大小。
这就很好比了,直接比 与 的大小就好了。
所以当 时,直接用 当做 的指数就好了。
但当 时,我们让 ,即 ,作为 的指数就好了。
那么对于所有的情况,我们先设一个 ,为 最终的指数。
然后我们从 到 枚举 ,如果 ,直接让 乘上 ,如果 ,直接停就好了。
然后快速幂就好了。
注意一下费马小定理的应用就好了。
就好了。
代码
点击查看代码
#include <bits/stdc++.h>
#define int long long
#define mod 998244353
using namespace std;
int n, a, ans = 1, tot = 1, x;
inline int quick_pow(int base, int ci) {
int res = 1;
while(ci) {
if(ci & 1)
res = res * base % mod;
base = base * base % mod;
ci >>= 1;
}
return res;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> a;
if(a == 1) {
putchar('1');
return 0;
}
for(int i = 2; i <= n; ++ i) {
cin >> x;
if(x == 1)
break;
tot *= x;
tot %= (mod - 1);
}
cout << quick_pow(a, tot) << '\n';
}
T2 u
题解
先设个东西,下面要用:
假设对于一个权值 ,我们去考虑所有权值小于 的边。
先来考虑给定的、权值小于 的边。假设这些边将图连成了 个连通块,块的大小分别为 。
再来考虑没有给定的、权值小于 的边。我们要让加边前后最小生成树的权值不变,那么我们需要让这些边所连接的两个端点在同一块内,否则最小生成树的边集一定会包含那个边。如果理解不了的话,你可以去考虑 Kruskal 的过程:我们要按边权从小到大枚举每一条边,如果当前边的两个端点在同一块内就不进行操作,否则将它们所在的块合并。那么对于这些没有给定的边,我们要让这些边的左右端点在同一块内,才不会对最终的最小生成树造成影响。
所以我们现在需要考虑,能否让所有权值小于 的、没有给定的边的两个端点在同一块内。不好直接判断,那么我们就去算一下当前局势还能容纳多少没有给定的边。
对于这 个块,我们不能合并任意两个块,那么一共最多会有 条边。所以当且仅当没有给定的、权值小于 的边数小于等于 块内相连的给定的边时,当前局势合法,也就是最终答案可能为 。
如果对于每一个 ,当前局势都是合法的,那么最终答案才为 ,只要有任一局势不合法,最终答案就为 。
考虑怎么实现。
维护块,并查集会吧?
维护块的大小,并查集的 会吧?
维护块内相连的边的数量,将一个点的出边转给另一个点会吧?
然后我们再来考虑其他的。
不难发现,我们不需要对于每一个权值都判断一下是否合法(,每个权值都判断会 T 飞),因为保证给出的 条边能使所有点连通,并且如果没有给定边权为 的边且 的局势合法,那么 的局势一定也合法,所以我们只需要枚举给出的 个边权即可。
然后考虑怎么维护 。假设这次操作将大小为 、 的两个块合并,那么新的 等于原来的 。
嗯,没有了,自己打去吧。
感觉我说的不是人话。
代码
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 500005;
struct E {
int w;
int u;
int v;
}edge[N];
int n, m, sum, fa[N], siz[N], tot;
vector<int> e[N];
inline bool cmp(E a, E b) {return a.w < b.w;}
inline int findf(int x) {
while(x != fa[x])
x = fa[x] = fa[fa[x]];
return x;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; ++ i)
fa[i] = i, siz[i] = 1;
for(int i = 1; i <= m; ++ i) {
cin >> edge[i].u >> edge[i].v >> edge[i].w;
e[edge[i].u].push_back(edge[i].v);
e[edge[i].v].push_back(edge[i].u);
}
stable_sort(edge + 1, edge + 1 + m, cmp);
for(int i = 1; i <= m; ++ i) {
if(edge[i].w - i > sum - tot) {
cout << "No\n";
return 0;
}
int fu = findf(edge[i].u), fv = findf(edge[i].v);
if(fu != fv) {
if(siz[fu] > siz[fv])
swap(fu, fv);
sum += siz[fu] * siz[fv];
for(int j = 0; j < e[fu].size(); ++ j) {
if(findf(e[fu][j]) != fv)
e[fv].push_back(e[fu][j]);
else
++ tot;
}
siz[fv] += siz[fu];
fa[fu] = fv;
}
}
cout << "Yes\n";
}
T3 a
题解
Subtask 1:
暴力枚举。
Subtask 2:
状压 dp。题解是这么说的,但我不知道压啥。
Subtask 3:
单降了都,那就是个栈。卡特兰数。单增输出 就行。
Subtask 4:
一个 序列合法,当且仅当 和 的数量与 序列相同,并且对于每一个 , 的长度为 的前缀中 的数量都不少于 的长度为 的前缀中 的数量。
所以直接 dp 即可。
Subtask 5:
已经很接近正解了。
我们设 在 序列和 序列中的位置分别为 ,即 。
考虑到,这是一个小根堆,而且这是一个排列,所以我们拿出 就意味着堆此时空了。所以 的前 个元素就是 的前 个元素,只不过顺序可能不太一样。所以 ,且 。
然后我们已经知道 ,所以我们可以把原问题看做两个子问题: 和 。其中, 是由 去掉 经过一些操作变化而来的,而 是由 经过一些操作变化而来的。
所以我们考虑递归地解决子问题。
找当前序列的最大值,然后枚举其在 中的位置,然后分成两个子序列,分治。注意左边要把最大值删去。不难看出来,子序列 一定是原序列 的某一区间 删掉一些较大数后得到的。
整合一下现在的信息,我们发现持续在变化的当前只有处理的子序列 所对应的 ,以及删掉了哪些数。所以我们设 表示 中,不超过 的数构成的子序列的答案,或者说是情况数。
考虑怎么转移。
-
当 不在 时,,这很显然。
-
当 在 时,我们假设 ,那么我们再去枚举 在 中的位置 ,就有:
如果你看了官方题解那个式子,你会发现不太一样,但是结果是一样的,因为这是一个排列,所以在 中不会再出现一个 ,所以 ,至于为什么我这么写,因为我觉得这样可能更好理解。
直接 dp 就行了,时间复杂度 ,能过。
Subtask 1 5:
我们考虑怎么处理 是一个数列而不是排列的情况。
假设一个数 出现次数大于 ,那么我们将其第 次出现设为 。
首先,我们约定,如果堆中最小值为 ,并且堆中存在多个 ,那么我们在取的时候先取下标最小的,也就是值相等的情况下满足先进先出的原则。
换句话说, 当且仅当 ,或者 且 。
那么在我们约定的条件下,可以把原数列转化为一个排列。
考虑这样转化的正确性。
转化后 序列中的每一个数都能唯一对应未转化的 序列中的一个数,而且如果 ,那么一定有 在 中排在 之前,这样就得到了一个自然双射,也就完成了证明。
代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 105;
const int mod = 1000000007;
int n, a[N], b[N], cnt[N], rk[N], f[N][N][N];
signed main() {
cin >> n;
for(int i = 1; i <= n; ++ i)
scanf("%d",a + i), ++ cnt[a[i]];
for(int i = 1; i <= n; ++ i)
cnt[i] += cnt[i - 1];
for(int i = n; i; -- i)
b[i] = cnt[a[i]] --, rk[b[i]] = i;
for(int i = 1; i <= n + 1; ++ i)
fill(f[i][i - 1], f[i][i - 1] + n + 1, 1);
for(int l = n; l; -- l) {
for(int r = l; r <= n; ++ r) {
f[l][r][0] = 1;
memcpy(f[l][r] + 1, f[l][r - 1] + 1, 4 * b[r] - 4);
memcpy(f[l][r] + 1, f[l + 1][r] + 1, 4 * b[l] - 4);
for(int i = max(b[l], b[r]); i <= n; ++ i)
if(rk[i] < l || rk[i] > r)
f[l][r][i] = f[l][r][i-1];
else
for(int j = rk[i]; j <=r; ++ j)
if(b[j] <= i)
f[l][r][i] = (f[l][r][i] + 1ll * f[l][j][i - 1] * f[j + 1][r][i]) % mod;
}
}
cout << f[1][n][n];
}
T4 y
求教教,我只能看懂题解的一小部分。不太擅长串串和图论。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现