【刷题】CSP-S/NOIP提高组 真题题解总结 & 近20年题目
PS:整理近20年CSP-S/NOIP提高组 的真题,提供思路和总结。
建议看完思路后自己实现代码,锻炼代码能力~
如需转载请联系我~
DP:
-
线性dp
-
P1091 [NOIP2004 提高组] 合唱队形
比较简单的一道题。求出以
结尾的最长上升子序列和以 为头的最长下降子序列,相加 即可。 -
P1052 [NOIP2005 提高组] 过河
如果不考虑
的范围,那么就是一道简单的线性 dp 。但是
很大,石头数量很少,所以每相邻两个石头的空隙一定很大。前置知识:P3951 [NOIP2017 提高组] 小凯的疑惑给定两个数
和 ,他们最大不能凑出的数是 。所以中间空隙过多一定是可以被凑出来的,即可以压缩中间的空隙,然后线性 dp 即可。 -
P1006 [NOIP2008 提高组] 传纸条
求出
条从 到 的路径。重复只算一次,求最大路径和。水题,不多赘述。记录
表示第一条走到 , 第二条走到 。由于 ,可以记录他们的和,优化掉一维。 -
P1541 [NOIP2010 提高组] 乌龟棋
记录
表示用了 个 号卡片, 个 号卡片, 个 号卡片, 个 号卡片。暴力转移即可
-
P2679 [NOIP2015 提高组] 子串
前缀和优化线性 dp。(推式子题)
首先考虑如何DP,然后再考虑如何优化。
状态表示:
表示只用 的前 个字母,选取了 段,可以匹配 的前 个字母的方案数。状态计算: 将
表示的所有方案分成两大类:- 不用
,则方案数是 ; - 使用
,则方案数是 ,满足 , 从小到大枚举。
时间复杂度是
。我们发先
的第二项和 很像,可以考虑维护一个 , 。 , 。
(把式子展开可以发现
的规律)。然后空间会爆,写滚动数组或者 01背包 倒序枚举优化掉第一维即可。
- 不用
-
P5664 [CSP-S2019] Emiya 家今天的饭
求解所有方案数
表示前 种烹饪方式,做了 道菜的方案数。-
状态转移:
第 种烹饪方式不做 -
第
种烹饪方法做 道主要食材是 的菜:
所有方案数量
。求解不合法方案:
表示前 中烹饪方法,越界食材数 其他食材数 为 的方案数。状态转移:
-
第
种烹饪方法不选: -
选越界食材
: -
选其他食材
所有方案数量:
。 即可。时间复杂度
。Tips: 做差有可能为负数,把所有状态加一个
的偏移量即可。 -
-
总结:
对于线性 dp的问题,一般状态定义为
,状态可能很多,所以可能有很多维。状态转移:考虑这个集合是由谁构成的,进行分类。比如分成 选
和不选 ......优化:如果状态过多,考虑滚动数组或者背包优化,转移中有求和,求最值等考虑用前缀和,单调队列优化。
-
-
区间dp
-
P1040 [NOIP2003 提高组] 加分二叉树
考虑到左边和右边独立,可以想到区间 dp 。
设
为 区间 的最大价值。转移:
因为要求出具体方案,可以边算边求,也可以算完答案反推回去。
-
P1063 [NOIP2006 提高组] 能量项链
很模板的一道题。和上一题类似。
设
为 区间 的最大价值。转移:
。套路:枚举长度
,枚举左端点 ,计算右端点 ,进行转移。Tips: 环上问题,破环为链,
倍长度即可。 -
P1005 [NOIP2007 提高组] 矩阵取数游戏
考虑到每一行是独立的,可以做
次dp设
表示将 这段数取完的最大值。转移
-
取左端点:
-
取右端点:
取
即可。需要高精度,附代码:
#include <bits/stdc++.h> using namespace std; const int N = 85, M = 31; int n, m,w[N][N],f[N][N][M],p[N][M],ans[M]; void mul(int a[], int b[], int c){ static int tmp[M]; for (int i = 0, t = 0; i < M; i ++ ){ t += b[i] * c; tmp[i] = t % 10; t /= 10; } memcpy(a, tmp, M * 4); } void add(int a[], int b[], int c[]){ static int tmp[M]; for (int i = 0, t = 0; i < M; i ++ ){ t += b[i] + c[i]; tmp[i] = t % 10; t /= 10; } memcpy(a, tmp, M * 4); } int compare(int a[], int b[]){ for (int i = M - 1; i >= 0; i -- ) if (a[i] > b[i]) return 1; else if (a[i] < b[i]) return -1; return 0; } void print(int a[]){ int k = M - 1; while (k && !ans[k]) k -- ; while (k >= 0) printf("%d", ans[k -- ]); puts(""); } void work(int w[]){ int a[M], b[M]; for (int len = 1; len <= m; len ++ ) for (int i = 0; i + len - 1 < m; i ++ ){ int j = i + len - 1; int t = m - j + i; mul(a, p[t], w[i]), mul(b, p[t], w[j]); add(a, a, f[i + 1][j]); if (j) add(b, b, f[i][j - 1]); if (compare(a, b) > 0) memcpy(f[i][j], a, M * 4); else memcpy(f[i][j], b, M * 4); } add(ans, ans, f[0][m - 1]); } int main(){ scanf("%d%d", &n, &m); for (int i = 0; i < n; i ++ ) for (int j = 0; j < m; j ++ ) scanf("%d", &w[i][j]); p[0][0] = 1; for (int i = 1; i <= m; i ++ ) mul(p[i], p[i - 1], 2); for (int i = 0; i < n; i ++ ) work(w[i]); print(ans); return 0; }
-
-
P7914 [CSP-S 2021] 括号序列
区间dp & 分类讨论
状态定义:
设
表示从 到 合法序列数量。但是不同的形态可能会有不同的转移。
将两维的dp扩充为三维,第三维表示不同的形态种类,dp状态就变成了
。-
: 形态如***...*
的括号序列(即全部是*
)。 -
: 形态如(...)
的括号序列(即左右直接被括号包裹且最左边括号与最右边的括号相互匹配)。 -
: 形态如(...)**(...)***
的括号序列(即左边以括号序列开头,右边以*
结尾)。 -
: 形态如(...)***(...)*(...)
的括号序列(即左边以括号序列开头,右边以括号序列结尾,注意:第2种形态也属于这种形态)。 -
: 形态如***(...)**(...)
的括号序列(即左边以*
开头,右边以括号序列结尾)。 -
: 形态如***(...)**(...)**
的括号序列(即左边以*
开头,右边以*
结尾,注意:第1种形态也属于这种形态)。
满足 ,有 。状态转移:
-
(直接特判) -
表示第 位与第 位能否配对成括号,能则为 ,否则为 。- 加括号时,里面可以是全
*
,可以是有一边是*
,也可以是两边都不是*
,唯独不能两边都是*
且中间有括号序列。
-
- 左边以括号序列开头且以括号序列结尾的是第3种,右边接一串
*
,是第0种。
- 左边以括号序列开头且以括号序列结尾的是第3种,右边接一串
-
- 左边以括号序列开头,结尾随便,符合的有第2和第3种,右边接一个括号序列,是第1种。
- 记得加上直接一个括号序列的。
-
- 左边以
*
开头,结尾随便,符合的有第4和第5种,右边接一个括号序列,是第1种。
- 左边以
-
- 左边以
*
开头,以括号序列结尾,符合的是第4种,右边接一串*
,是第0种。 - 记得加上全是
*
的。
- 左边以
答案:
。时间复杂度:
。 -
-
总结:
对于区间dp的问题,一般状态定义为
表示区间 的...(最大值最小值等)条件 每个区间相互独立,互不影响。
转移: 外层枚举区间长度,内层枚举起点
,算出终点 ,再进行转移。Tips: 如果状态里只包含区间不够,则考虑加维。
(例如)P7914 [CSP-S 2021] 括号序列
-
-
背包
-
[NOIP2006 提高组] 金明的预算方案
分组背包问题。
将每个组件和任意个附件看成一组,每种情况相互独立,所以可以看成分组背包。
先枚举组,在倒序枚举体积,然后枚举一个二进制状态,最后枚举二进制的每一位,算出
和 。 -
P1941 [NOIP2014 提高组] 飞扬的小鸟
模拟 & 背包 & 细节。
设
表示横坐标为 ,纵座标为 的最少点击次数。时间复杂度:
优化: 考虑「点击
次」和「点击 次」之间的联系。最优方案中,点击了 次到达纵坐标 ,则如果点击 次,会到达纵坐标 。类似完全背包的优化思路。
。即,「只点击一次」和「从点击若干次到达的将要位置上再点击一次」。
超过区域的状态应该设为
,答案简单统计即可。 -
P5020 [NOIP2018 提高组] 货币系统
排序 & 完全背包。
思路很好想,从小到大排序。
大的一定是从小的凑出来的,所以类似筛法,进行
|
运算即可。代码:
sort(a, a + n); memset(f, 0, sizeof f); f[0] = true; int res = n; for (int i = 0; i < n; i ++ ){ if (f[a[i]]) res -- ; else for (int j = a[i]; j <= a[n - 1]; j ++ ) f[j] |= f[j - a[i]]; } cout<<res<<endl;
-
总结:
熟记背包模板(01背包,完全背包,多重背包,分组背包)。
背包优化: 倒序枚举体积优化掉第一维。
-
-
状压dp
-
[NOIP2017 提高组] 宝藏
状压dp。
设
为 包含状态为 的点,且高度为 的最小花费。状态转移:
。 为从 到 的花费。Tips:
枚举真子集:
for (int i = 1; i < 1 << n; i ++ ) for (int j = (i - 1) & i; j; j = (j - 1) & i)
枚举子集:
for (int i = 1; i < 1 << n; i ++ ) for (int j = i; j; j = (j - 1) & i)
-
P2831 [NOIP2016 提高组] 愤怒的小鸟
抛物线方程为:
只有两个未知数,可以用两个点确定这条抛物线。
预处理出最多
条合法抛物线,然后用这些抛物线对 点集 进行覆盖即可。对于两点构成的抛物线,我们还要处理出他穿过的其他的点。
然后进行简单的状压 dp 即可。
时间复杂度:
-
总结:
如果一道题目中
很小( ,可以考虑状压 dp。转移一般是从枚举的状态的子集转移过来等。
枚举子集的时间复杂度
。
-
-
树形dp
-
P5658 [CSP-S2019] 括号树
-
当树为链时:
设 表示以 结尾的合法方案数量。显然:
, 和 匹配。 -
当树不为链时:
我们观察上面的式子,
在链上表示 的前一个,在树上其实就是表示 的父节点。
一个 dfs 即可完成。
-
-
P5024 [NOIP2018 提高组] 保卫王国
树形 dp & 倍增。
状态定义:
-
表示选以 为根的子树,且不选择 的最小花费。 表示选以 为根的子树,且选择 的最小花费。 -
表示选除了以 为根的子树的所有点,且不选择 的最小花费。 表示选除了以 为根的子树的所有点,且选择 的最小花费。 -
表示从 开始往上跳 步,设跳到了点 ,且 的选择状态为 ( 不选, 选), 的选择状态为 ( 不选, 选) ,以 为根的子树 减 以 为根的子树,剩余部分的最小花费。 -
表示从 开始往上跳 个点到达的点。 -
表示 的深度。
状态转移:
通过简单的计算可以得出,可以画图辅助理解。附代码:
#include <bits/stdc++.h> using namespace std; typedef long long LL; const int N = 100010, M = N * 2, K = 17; int n, m; int p[N]; int h[N], e[M], ne[M], idx; LL f[N][2], g[N][2], w[N][K][2][2]; int fa[N][K], depth[N]; void add(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ; } void dfs_f(int u, int father) { f[u][1] = p[u]; for (int i = h[u]; ~i; i = ne[i]) { int j = e[i]; if (j == father) continue; dfs_f(j, u); f[u][0] += f[j][1]; f[u][1] += min(f[j][0], f[j][1]); } } void dfs_g(int u, int father) { for (int i = h[u]; ~i; i = ne[i]) { int j = e[i]; if (j == father) continue; g[j][0] = g[u][1] + f[u][1] - min(f[j][0], f[j][1]); g[j][1] = min(g[j][0], g[u][0] + f[u][0] - f[j][1]); dfs_g(j, u); } } void dfs_fa(int u, int father) { for (int i = h[u]; ~i; i = ne[i]) { int j = e[i]; if (j == father) continue; depth[j] = depth[u] + 1; fa[j][0] = u; for (int k = 1; k < K; k ++ ) fa[j][k] = fa[fa[j][k - 1]][k - 1]; dfs_fa(j, u); } } void dfs_w(int u, int father) { for (int i = h[u]; ~i; i = ne[i]) { int j = e[i]; if (j == father) continue; w[j][0][0][1] = w[j][0][1][1] = f[u][1] - min(f[j][0], f[j][1]); w[j][0][1][0] = f[u][0] - f[j][1]; for (int k = 1; k < K; k ++ ) for (int x = 0; x < 2; x ++ ) for (int y = 0; y < 2; y ++ ) for (int z = 0; z < 2; z ++ ) w[j][k][x][y] = min(w[j][k][x][y], w[j][k - 1][x][z] + w[fa[j][k - 1]][k - 1][z][y]); dfs_w(j, u); } } LL calc(int a, int x, int b, int y) { if (depth[a] < depth[b]) swap(a, b), swap(x, y); if (!x && !y && fa[a][0] == b) return -1; LL sa[2], sb[2], na[2], nb[2]; memset(sa, 0x3f, sizeof sa); memset(sb, 0x3f, sizeof sb); sa[x] = f[a][x], sb[y] = f[b][y]; for (int i = K - 1; i >= 0; i -- ) if (depth[fa[a][i]] >= depth[b]) { memset(na, 0x3f, sizeof na); for (int u = 0; u < 2; u ++ ) for (int v = 0; v < 2; v ++ ) na[v] = min(na[v], sa[u] + w[a][i][u][v]); memcpy(sa, na, sizeof sa); a = fa[a][i]; } if (a == b) return sa[y] + g[b][y]; for (int i = K - 1; i >= 0; i -- ) if (fa[a][i] != fa[b][i]) { memset(na, 0x3f, sizeof na); memset(nb, 0x3f, sizeof nb); for (int u = 0; u < 2; u ++ ) for (int v = 0; v < 2; v ++ ) { na[v] = min(na[v], sa[u] + w[a][i][u][v]); nb[v] = min(nb[v], sb[u] + w[b][i][u][v]); } memcpy(sa, na, sizeof sa); memcpy(sb, nb, sizeof sb); a = fa[a][i]; b = fa[b][i]; } int lca = fa[a][0]; LL res0 = f[lca][0] + g[lca][0] - f[a][1] - f[b][1] + sa[1] + sb[1]; LL res1 = f[lca][1] + g[lca][1] - min(f[a][0], f[a][1]) - min(f[b][0], f[b][1]) + min(sa[0], sa[1]) + min(sb[0], sb[1]); return min(res0, res1); } int main(){ scanf("%d%d%*s", &n, &m); for (int i = 1; i <= n; i ++ ) scanf("%d", &p[i]); memset(h, -1, sizeof h); for (int i = 0; i < n - 1; i ++ ) { int a, b; scanf("%d%d", &a, &b); add(a, b), add(b, a); } dfs_f(1, -1); dfs_g(1, -1); depth[1] = 1; dfs_fa(1, -1); memset(w, 0x3f, sizeof w); dfs_w(1, -1); while (m -- ){ int a, x, b, y; scanf("%d%d%d%d", &a, &x, &b, &y); printf("%lld\n", calc(a, x, b, y)); } return 0; }
-
-
树形dp 问题,可以考虑先考虑树为一条链的时候,就转换成了线性dp。
通常情况下状态定义为
表示以 子树的某种状态。转移一般有两种,可以用儿子的信息更新父亲,也可以用父亲的信息更新儿子。
-
-
倍增优化dp
-
P1081 [NOIP2012 提高组] 开车旅行
dp & 倍增
状态定义:
表示小 A 从城市 出发,会走到哪个城市 表示小 B 从城市 出发,会走到哪个城市 表示从城市 出发,小 A 先走,走 步会走到哪个城市 表示从城市 出发,小 B 先走,走 步会走到哪个城市 表示从城市 出发,小 A 先走,走 步的小 A 走的总距离 表示从城市 出发,小 B 先走,走 步的小 A 走的总距离 表示从城市 出发,小 A 先走,走 步的小 B 走的总距离 表示从城市 出发,小 B 先走,走 步的小 B 走的总距离状态计算:
通过简单的计算可以得出,可以画图辅助理解。附代码:
#include <iostream> #include <cstring> #include <algorithm> #include <set> #define x first #define y second using namespace std; typedef long long LL; typedef pair<LL, int> PLI; const int N = 100010, M = 17; const LL INF = 1e12; int n; int h[N]; int ga[N], gb[N]; int f[2][N][M]; LL da[2][N][M], db[2][N][M]; void init_g() { set<PLI> S; S.insert({INF, 0}), S.insert({INF + 1, 0}); S.insert({-INF, 0}), S.insert({-INF - 1, 0}); PLI cand[4]; for (int i = n; i; i -- ) { PLI t(h[i], i); auto j = S.upper_bound(t); j ++ ; for (int k = 0; k < 4; k ++ ) { cand[k] = *j; j -- ; } LL d1 = INF, d2 = INF; int p1 = 0, p2 = 0; for (int k = 3; k >= 0; k -- ) { LL d = abs(h[i] - cand[k].x); if (d < d1) { d2 = d1, d1 = d; p2 = p1, p1 = cand[k].y; } else if (d < d2) { d2 = d; p2 = cand[k].y; } } ga[i] = p2, gb[i] = p1; S.insert(t); } } void init_f() { for (int i = 1; i <= n; i ++ ) { f[0][i][0] = ga[i]; f[1][i][0] = gb[i]; } for (int j = 1; j < M; j ++ ) for (int i = 1; i <= n; i ++ ) for (int k = 0; k < 2; k ++ ) { if (j == 1) f[k][i][j] = f[1 - k][f[k][i][0]][0]; else f[k][i][j] = f[k][f[k][i][j - 1]][j - 1]; } } int get_dist(int a, int b) { return abs(h[a] - h[b]); } void init_d() { for (int i = 1; i <= n; i ++ ) { da[0][i][0] = get_dist(i, ga[i]); db[1][i][0] = get_dist(i, gb[i]); } for (int j = 1; j < M; j ++ ) for (int i = 1; i <= n; i ++ ) for (int k = 0; k < 2; k ++ ) { if (j == 1) { da[k][i][j] = da[k][i][j - 1] + da[1 - k][f[k][i][j - 1]][j - 1]; db[k][i][j] = db[k][i][j - 1] + db[1 - k][f[k][i][j - 1]][j - 1]; } else { da[k][i][j] = da[k][i][j - 1] + da[k][f[k][i][j - 1]][j - 1]; db[k][i][j] = db[k][i][j - 1] + db[k][f[k][i][j - 1]][j - 1]; } } } void calc(int s, int x, int& la, int& lb) { la = lb = 0; for (int i = M - 1; i >= 0; i -- ) if (f[0][s][i] && la + lb + da[0][s][i] + db[0][s][i] <= x) { la += da[0][s][i], lb += db[0][s][i]; s = f[0][s][i]; } } int main() { scanf("%d", &n); for (int i = 1; i <= n; i ++ ) scanf("%d", &h[i]); init_g(); init_f(); init_d(); int x; scanf("%d", &x); int res = 0, max_h = 0; double min_ratio = INF; for (int i = 1; i <= n; i ++ ) { int la, lb; calc(i, x, la, lb); double ratio = lb ? (double)la / lb : INF; if (ratio < min_ratio || ratio == min_ratio && h[i] > max_h) { min_ratio = ratio; max_h = h[i]; res = i; } } printf("%d\n", res); int m; scanf("%d", &m); while (m -- ) { int s, x, la, lb; scanf("%d%d", &s, &x); calc(s, x, la, lb); printf("%d %d\n", la, lb); } return 0; }
-
总结:
倍增优化dp,可以先考虑朴素dp,结合
的范围可以确定是否可以倍增优化。这类题目难度一般比较大
-
-
dp 杂题:
-
P1850 [NOIP2016 提高组] 换教室
dp & 期望 & Floyd
状态表示:
表示前 个课程,申请换了 次,且最后一次没申请换的最小期望长度。 表示前 个课程,申请换了 次,且最后一次申请交换的最小期望长度。则
在如下两种情况中取最小值即可:-
第
个课程没申请交换,最小期望是 -
第
个课程申请交换,最小期望是
推导类似。时间复杂度:
-
-
P1514 [NOIP2010 提高组] 引水入城
记忆化搜索 & 贪心(区间覆盖)
第一问直接 DFS + 记忆化即可。
可以证明每个点覆盖到的一定是一段区间。
如果中间有空隙,那么一定会有另一条水流流向那个点,而原来的水流也可以经过 这两条水流的交点 到达哪个空隙,矛盾。
简单的搜索加上区间覆盖模板即可通过。
-
总结:
dp 问题很复杂,难点在于状态定义。
一般有一些固定的套路,如线性 dp,区间 dp 等。
状态转移就是 集合划分,考虑这个集合可以划分成哪几种不重不漏的集合,然后进行转移。
初值和边界很重要,一般按照状态定义来写。
总之 dp问题难想 & 细节多,一定要仔细想 & 仔细检查。
一些dp模板也很重要(背包,数位 dp 等)。
-
贪心:
-
P1090 [NOIP2004 提高组] 合并果子
贪心 & 堆优化
很明显每次取出最小的两个合并是最优的。
可以用堆优化。
时间复杂度
。 -
P1080 [NOIP2012 提高组] 国王游戏
贪心 & 高精度
大胆猜结论:按照
从小到大排序为最优解。证明:
假设将两个人的位置互换,考虑他们在交换前和交换后所获得的奖励是多少:
-
交换前:
-
第
个人 -
第
个人
-
-
交换后:
- 第
个人 - 第
个人
- 第
将每个数除以
,然后乘 ,得到:-
交换前
-
第
个人 -
第
个人
-
-
交换后
-
第
个人 -
第
个人
-
由于
,所以 ,并且 ,所以 ,所以交换后两个数的最大值不大于交换前两个数的最大值。
证毕!
-
-
P5019 [NOIP2018 提高组] 铺设道路
贪心
如果
那么答案加上 。因为如果
那么在填 的时候一定能把 一块填上。否则给 填上 ,枚举到 的时候只需要再填 即可。 -
P1969 [NOIP2013 提高组] 积木大赛
和 P5019 [NOIP2018 提高组] 铺设道路 一样,不多赘述。 -
P1970 [NOIP2013 提高组] 花匠
贪心。
转化题意,就是让我们求出最长的一个波动序列的长度。
序列中的每一个极值点(波峰,波谷)都是可以选择的,于是我们统计出所有的极值点即可(可以证明不存在比它更优的结果)。
-
P1315 [NOIP2011 提高组] 观光公交
贪心 & 递推
预处理出每个站台的最早发车时间
,每个站台下车的人数 。接下来求出车最早到达每个站台的时间
, 其中 是从第i 个站台走到第 个站台的时间。那么每个乘客的旅行时间就是
,其中 是乘客的终点站, 是乘客到达起点的时间。考虑氮气加速用在哪里
可以发现如下几个性质:
-
每次加速一段之后,可能会影响接下来一段连续的站点。因此在区间内部,加速最左端的站点一定是最优的。
-
不同红色区间之前完全独立,加速其中某个区间时,对其余区间没有任何影响。
-
加速某个区间左端点之后,该区间可能会分裂成两个子区间,这两个子区间的加速效果小于等于原区间的加速效果。
每次选择当前节约时间最多的一段即可。
时间复杂度
。 -
-
P7078 [CSP-S2020] 贪吃蛇
太难了不想写了,如下:题解 ------ @ OMG_wc
-
P5665 [CSP-S2019] 划分
太难了不想写了,如下:题解 ------ @ 1saunoya
-
总结:
贪心题目可以先考虑 dp,如果dp比较好做就选择使用dp,如果dp不好做并且贪心策略大致是正确的时候可以选择贪心。
贪心题就要大胆才猜结论,写个程序跑一下样例过了一般就对了。
我们 OIer 做题不需要证明,跑一下即可。
搜索:
-
DFS
-
BFS
-
剪枝
数学:
-
数论:
-
组合数学
图论:
-
拓扑排序
-
树
-
LCA
-
最短路
-
二分图
数据结构:
-
堆
-
并查集
-
线段树
-
单调队列
基础算法:
-
P1089 [NOIP2004 提高组] 津津的储蓄计划
水
-
P1089 [NOIP2004 提高组] 津津的储蓄计划
水
-
P6032 选择客栈
双指针,枚举第二个客栈,前缀和求出所有与枚举的客栈颜色相同的数量,简单计算即可。
-
P1039 [NOIP2003 提高组] 侦探推理
枚举,枚举星期和罪犯,暴力判断是否满足即可
细节比较多。
-
P1053 [NOIP2005 提高组] 篝火晚会
转化题意后就是求出两个环上,通过旋转,不相等的数
量最少是多少。 -
P1065 [NOIP2006 提高组] 作业调度方案
把工作区间存下来,然后贪心往前插即可。
-
P1540 [NOIP2010 提高组] 机器翻译
模拟
-
P1003 [NOIP2011 提高组] 铺地毯
二维前缀和
-
P2038 [NOIP2014 提高组] 无线网络发射器选址
枚举
-
P2615 [NOIP2015 提高组] 神奇的幻方
模拟
-
P10804 [CEOI2024] 玩具谜题
模拟
-
P7075 [CSP-S2020] 儒略日
观察样例 ,到
年 天,而之后都是 年共 闰年的循环(共 )这个天数的循环。那么暴力预处理出来 这么多天的答案,对于任意 ,若 ,直接输出,若 ,答案月日就等同于 ,年份就是这个天数的年份 。时间复杂度
,询问 。 -
P1328 [NOIP2014 提高组] 生活大爆炸版石头剪刀布
就是比普通的石头剪刀布复杂一点点,模拟即可。
-
P1054 [NOIP2005 提高组] 等价表达式
判断表达式成立问题,可以采用近似做法。
代入一些数据如果都成立,我们可以近似认为是成立的。
-
P1098 [NOIP2007 提高组] 字符串的展开
按照题意模拟即可。
-
P1071 [NOIP2009 提高组] 潜伏者
按照题意模拟即可。
-
P1079 [NOIP2012 提高组] Vigenère 密码
按照题意模拟即可。
-
P3952 [NOIP2017 提高组] 时间复杂度
栈,模拟,字符串
循环的时间复杂度取决于最内层的计算次数,即嵌套最深的一层循环的计算次数。
循环的嵌套和括号序列的嵌套类似,所以我们可以借助栈来遍历整个代码序列。
当遇到FOR语句时,将该循环压入栈顶,当遇到END语句时,将栈顶的循环弹出。那么栈中从底到顶的序列就是当前循环从外到内嵌套的序列。
对于每层循环FOR i x y,我们先判断它的计算次数
: 是 时: 也是 ,那么循环次数是 ; 是正整数,由于 远大于100,且 在100以内,所以这个循环一次也不执行;
是正整数时: 是 ,那么会循环 次; 是正整数,如果 ,那么会循环 次,如果 ,那么一次也不执行;
然后判断整个循环嵌套序列的计算次数:
如果外层循环中的某一层执行次数是0或者当前循环的执行次数是0,那么当前这层的计算次数就是0;
否则当前这层的循环次数就是上一层循环的执行次数乘以前面判断出的循环次数 ;语法错误有两种:
- 对于当前循环创建的变量,如果在栈中已经出现过,说明与外面的某一层循环的循环变量重名了,产生语法错误;
- 如果遍历过程中对空栈执行弹出操作,或者遍历结束后栈不为空,说明FOR语句与END语句不匹配,产生语法错误。
时间复杂度
。 -
P1051 [NOIP2005 提高组] 谁拿了最多奖学金
水
-
P1097 [NOIP2007 提高组] 统计数字
水
-
P1966 [NOIP2013 提高组] 火柴排队
化简公式,可以得出就是要求逆序对的数量,归并排序或者树状数组计算即可。
-
P1314 [NOIP2011 提高组] 聪明的质监员
纯二分
-
P1083 [NOIP2012 提高组] 借教室
纯二分
-
P2678 [NOIP2015 提高组] 跳石头
纯二分
-
P5021 [NOIP2018 提高组] 赛道修建
很容易想到二分m条赛道中最小的那一个,也可以理解为画一条分界线mid,看看比mid大或者等于的赛道能不能有m个。
对于一个点,从它子树发源的赛道,最多只能有一条穿过它并向上贡献,因为父亲边是唯一的,多一条赛道肯定会在边上重复。
-
P7076 [CSP-S2020] 动物园
位运算。
细节:计算
会溢出,直接用计算器达标输出即可 -
P5657 [CSP-S2019] 格雷码
找规律
-
P7915 [CSP-S 2021] 回文
-
P8819 [CSP-S 2022] 星战
哈希
-
P5023 [NOIP2018 提高组] 填数游戏
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库