NOIP 2000 提高组算法题解

作者: | 更新日期:

涉及进制转换、动态规划、DFS 搜索等算法

本文首发于公众号:天空的代码世界,微信号:tiankonguse

零、背景

今天继续分享 2000 年 NOIP 提高组题解。

一、进制转换

题意:给一个十进制数字,求转化为一个负数进制的数字。

思路:数学公式

N 进制可以转化为递归的形式。
例如 N 进制的 X(10) = abcd(X) 展开形式与递归形式如下。
展开形式 a*N^3 + b*N^2 + c*N^1 + d
递归形式:((a*N + b) * N + c) * N + d
可以抽象为 X(10) = A * N + d

对于正数进制,第一位 d 就是 X%N
而对于负数进制,X%N 为正数时满足公式,可以递归处理。
X%N 为负数时,需要转化为正数,即再减去负数 N 即可,此时 A 的个数加一。

X(10) = A * N + d
      = (A + 1) * N + (d - N)  

转化为代码如下:

while (n) {
  int rem = n % r;
  n /= r;
  if (rem < 0) {
    rem -= r;
    n++;
  }
  ans.push_back(num[rem]);
}

复杂度:log(n)

二、乘积最大

题意:给一个数字字符串,插入 K 个乘号,使得乘积最大。

思路:动态规划+高精度

状态:dp(n,k) 前 n 个数字拆分为 k 个数字的最大乘积。
状态转移方程:枚举最后一个数字的位数。
复杂度:O(k*n^2)

  dp(n,k)
= max(dp(i, k-1) * Val(i+1, n))

三、单词接龙

题意:给 n 个单词,进行单词接龙,每个单词最多用两次,问接龙可以得到的最长长度是多少。
接龙规则:前面单词的后缀等于后面单词的前缀,但单词不能存在包含关系。
单词数量:20 个

思路:DFS 搜索

显然,如果两个单词有多种接龙方式时,选择公共前后缀最短的方式可以得到更长的答案。
故可以预处理任意两个单词之间是否可以接龙,记录最短的公共前后缀长度。
然后暴力从龙头递归搜索,并记录单词的使用次数,最后更新答案。
复杂度:O(n!*n!)

int visited[25];
int Dfs(const int u, const int len) {
  int ans = len;
  for (auto [v, pos] : g[u]) {
    if (visited[v] >= 2) continue;
    visited[v]++;
    ans = max(ans, Dfs(v, len + words[v].length() - pos));
    visited[v]--;
  }
  return ans;
}

扩展:如果 n 比较小时,可以使用状态压缩 mask 来避免重复搜索。
状态定义:dp[mask][n]
状态解释:已经选择 mask 个数字,剩余数字使用第 n 个单词当做龙头可以新增的长度。

四、方格取数

题意:给一个矩阵,只能从左上角往下与往右走到右下角。
现在可以走两次,问怎么走才能使得路径的数字和最大。
要求:同一个位置数字只能加一次。

思路:动态规划

状态定义:dp(x0,y0,x1,y1)

这道题的难点在于如何定义两个点的关系,从而避免重复计算答案。
这里对两个点做了相对位置的限制。
限制1:第一个点不能在第二个点的右边。
限制2:第一个点不能在第二个点的下边。

状态含义:左下方点位于 (x0,y0),右上方点位于 (x1,y1) 时的最优答案。

点的关系确定了,再确定移动规则,从而可以避免重复统计答案。
规则1:同点,第一个点只能向左走,第二个点只能向上走,此时不能增加答案。
规则2:同行,只能第二个点移动。
规则3:同列,只能第一个点移动。
规则4:其他,两个点都可以移动。

int Dfs(int r0, int c0, int r1, int c1) {
  int& ans = dp[r0][c0][r1][c1];
  if (ans != -1) {
    return ans;
  }
  ans = 0;
  if (r0 == 0 || c0 == 0 || r1 == 0 || c1 == 0) {
    return ans;
  } else if (r0 == r1 && c0 == c1) {
    ans = max(Dfs(r0, c0 - 1, r1, c1), Dfs(r0, c0, r1 - 1, c1));
  } else if (r0 == r1) {
    ans = max(Dfs(r0, c0, r1, c1 - 1), Dfs(r0, c0, r1 - 1, c1)) + g[r1][c1];
  } else if (c0 == c1) {
    ans = max(Dfs(r0 - 1, c0, r1, c1), Dfs(r0, c0 - 1, r1, c1)) + g[r0][c0];
  } else {
    int ans0 = max(Dfs(r0 - 1, c0, r1, c1), Dfs(r0, c0 - 1, r1, c1)) + g[r0][c0];
    int ans1 = max(Dfs(r0, c0, r1, c1 - 1), Dfs(r0, c0, r1 - 1, c1)) + g[r1][c1];
    ans = max(ans0, ans1);
  }
  return ans;
}

五、最后

这次比赛的四道题都不难,但是有不少注意事项。
第一题,负数进制转换如果没做过,就需要推导公式,而且存在一些理解难度。
第二题,考察大整数乘法。
第三题,题目有问题,只能暴力搜索。
第四题,可以想到状态,难在定义两个点的关系以及规则,否则就很容易发生重复计算问题。

《完》

-EOF-

本文公众号:天空的代码世界
个人微信号:tiankonguse
公众号 ID:tiankonguse-code

本文首发于公众号:天空的代码世界,微信号:tiankonguse
如果你想留言,可以在微信里面关注公众号进行留言。

关注公众号,接收最新消息

tiankonguse +
穿越