【NOI 2014】总结

本文分享了作者在准备NOI2014过程中遇到的挑战与解决方案,包括位运算、动态树、消除游戏等算法应用,以及动态最小生成树、KMP算法与倍增思想的巧妙使用。通过具体实例分析,揭示了高效求解策略与优化技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这两天做了一下NOI2014的题目,虽然分数并不理想,但暴露出的问题是挺重要的。

总体来说,这两天的题目都不算太难。

【Day 1】

起床困难综合症:给定初始值x0的取值范围,问经过一系列的位运算后,求得出来的答案的最大值。

考察的算法是二进制运算的位独立性质,以及高位更优性。

显然由于二进制下各位运算是独立的,那么我们每一位取0或取1各模拟一次操作,取最大值。

因为有对初始值取值范围的限制,那么从高位到低位贪心地选1,保证初始值不超过取值范围就可以了。

魔法森林:给定一幅无向图,边上有两个权值ai和bi,选定两个值A和B,一条边可以通过当且仅当A>=ai且B>=bi,问A+B至少为多少时,存在一条从点1到点n的路径。

考察的算法是动态树。

从小到大枚举A,那么随着A的增大,会有新的边加入图中。我们要做的是维护从1到n的路径上bi的最大值最小。这大致上就是一个动态最小生成树的模型。我们需要一个数据结构支持(1)改变树的形态,(2)查询路径上边权最大值。那么用动态树可以在O(log n)时间内解决这两个问题。(关于动态树的总结之后贴)

消除游戏:给定一个填满数字的四连通方格,每一次操作走一条不重叠的路径,使得路径上经过的数字串起来是一个质数或一个回文数,然后这个数字会被“消除”,在它上面的数字会“掉下来”。问可以得到最大得分的操作序列是什么。

这是一道提交答案题。在考场上需要对给定数据的特点仔细分析,设计不同的求解方案,刷出一个更优解。

从数据特点可以发现:

(1) 1、2点需要写完基本的程序。

(2) 3、4、5点需要找最长的质数

(3) 6、7、8、9、10点需要找最长的回文串。

*(4) 第6个点找回文串的优先顺序决定了解的优劣性

*(5) 第10个点是整一条回文串,全都可以消除。

而正确的解法,是将原表格分成若干个12 × 12 的小方格,小方格内的暴力做,定义一个入口与出口,表示小方格之间的转移。


【Day 2】

动物园:给定一个字符串s,求s的每一个前缀,有多少个 前缀的后缀 满足也是s的前缀,且不与s的前缀重叠。

考察的算法是KMP的next数组的灵活运用和倍增思想。

因为next数组的定义是某个前缀的 前缀的后缀,也是s的前缀。满足这一条件的 前缀的后缀 的最长长度是多少。

如果没有“重叠”的限制的话,那么原问题就相当于求沿next指针上跳的层数,dp即可。

如果加上了重叠的限制呢?

那么就要沿着next指针上跳,找到第一个位置j,满足j + j < i(这里的编号是从0到n-1),那么往上跳的所有层数都是满足条件的。

如何找到这个位置?

显然暴力的跳会超时,那么我们可以参照倍增思想,记f[i][j]为从位置j沿next指针往上跳2^i次到达的位置,沿f数组上溯即可。

随机数生成器:给定一套随机序列生成的规则,生成一个包含1~n*m排列的方阵,问从左上角走到右下角形成的序列排序后字典序最小的序列是什么。

考察的算法是贪心。

显然1是一定可以取到的,那么原问题就转化为两个子问题,从左上角到1位置的方阵序列,和1位置到右下角的方阵序列。

(注意存储每个数字的坐标要用short,不然会爆空间。)

当时我写的是splay来维护当前的序列,但这样的时间复杂度是O(n * m * log(n + m))的。反而暴力地打上标记,配合break可以达到O (n * m) 的效果。

实际上可以维护每一列的最高位置与最低位置为多少,直接判断这一个点是否在这一范围内就可以了。

P2375 [NOI2014] 动物园是一道经典的动态规划题目,以下是该题的详细题意和解题思路。 【题意描述】 有两个长度为 $n$ 的整数序列 $a$ 和 $b$,你需要从这两个序列中各选出一些数,使得这些数构成一个新的序列 $c$。其中,$c$ 序列中的元素必须在原序列中严格递增。每个元素都有一个价值,你的任务是选出的元素的总价值最大。 【解题思路】 这是一道经典的动态规划题目,可以采用记忆化搜索的方法解决,也可以采用递推的方法解决。 记忆化搜索的代码如下: ```c++ #include <iostream> #include <cstdio> #include <cstring> using namespace std; const int MAXN = 1005; int dp[MAXN][MAXN], a[MAXN], b[MAXN], n; int dfs(int x, int y) { if (dp[x][y] != -1) return dp[x][y]; if (x == n || y == n) return 0; int res = max(dfs(x + 1, y), dfs(x + 1, y + 1)); if (a[x] > b[y]) { res = max(res, dfs(x, y + 1) + b[y]); } return dp[x][y] = res; } int main() { scanf("%d", &n); for (int i = 0; i < n; i++) scanf("%d", &a[i]); for (int i = 0; i < n; i++) scanf("%d", &b[i]); memset(dp, -1, sizeof(dp)); printf("%d\n", dfs(0, 0)); return 0; } ``` 其中,dp[i][j]表示选到a数组中第i个元素和b数组中第j个元素时的最大价值,-1表示未计算过。dfs(x,y)表示选到a数组中第x个元素和b数组中第y个元素时的最大价值,如果dp[x][y]已经计算过,则直接返回dp[x][y]的值。如果x==n或者y==n,表示已经遍历完一个数组,直接返回0。然后就是状态转移方程了,如果a[x] > b[y],则可以尝试选b[y],递归调用dfs(x, y+1)计算以后的最大价值。否则,只能继续遍历数组a,递归调用dfs(x+1, y)计算最大价值。最后,返回dp[0][0]的值即可。 递推的代码如下: ```c++ #include <iostream> #include <cstdio> #include <cstring> using namespace std; const int MAXN = 1005; int dp[MAXN][MAXN], a[MAXN], b[MAXN], n; int main() { scanf("%d", &n); for (int i = 0; i < n; i++) scanf("%d", &a[i]); for (int i = 0; i < n; i++) scanf("%d", &b[i]); for (int i = n - 1; i >= 0; i--) { for (int j = n - 1; j >= 0; j--) { dp[i][j] = max(dp[i + 1][j], dp[i + 1][j + 1]); if (a[i] > b[j]) { dp[i][j] = max(dp[i][j], dp[i][j + 1] + b[j]); } } } printf("%d\n", dp[0][0]); return 0; } ``` 其中,dp[i][j]表示选到a数组中第i个元素和b数组中第j个元素时的最大价值。从后往前遍历数组a和数组b,依次计算dp[i][j]的值。状态转移方程和记忆化搜索的方法是一样的。 【参考链接】 P2375 [NOI2014] 动物园:https://www.luogu.com.cn/problem/P2375
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值