NOIP 2002 普及组算法题解

作者: | 更新日期:

涉及枚举、搜索、高精度乘法、动态规划等算法

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

零、背景

今天继续分享 2002 年 NOIP 普及组题解。

一、级数求和

题意:对于 S(n)=sum(1/n) 的级数求和,求最小的 n 使得 S(n)>k

思路:枚举

从小到大枚举求和,直到找到答案即可。

double sum = 0;
int n = 0;
while (sum <= k) {
  n++;
  sum += 1.0 / n;
}
printf("%d\n", n);

二、选数

题意:给 n 个数,选 k 个数,问存在多少个方案满足 k 个数的和为质数。

思路:搜索

先预处理出素数表,然后深搜枚举所有 C(n,k) 中情况,判断是否是素数。

由于 n 不大于 20,故最坏情况为 C(20, 10),不超过 1000^2

int dfs(const int num, const ll sum, const int pos) {
  if(num + (n - pos) < k) return 0; // 剪枝
  if (num == k) {
    return isprime(sum);
  }
  int ans = 0;
  ans += dfs(num, sum, pos + 1); // 不选择
  ans += dfs(num + 1, sum + a[pos], pos + 1); // 选择
  return ans;
}

选择的数字和最大为 10^8,素数表存不下。
这时候只需要筛选出根号个素数即可。
小于根号的,查素数表,大于根号的,暴力判断是不是素数。

int isprime(int x) {  // 判断一个数是否是素数
  if (x < N) return is[x];
  for (int i = 0; i < prmCnt; i++) {  // 遍历所有素数
    if (x % prm[i] == 0) {
      return 0;
    }
  }
  return 1;
}

三、产生数

题意:给一个大整数,现在可以对某一位的数字替换为其他数字,问最终可以替换出多少个不同的数字。

思路:高精度乘法

根据替换规则建图,然后 dfs 预处理计算出每个数字可以替换为哪些数字,从而可以确定每一位可以替换为哪些数字。
数字总个数为各位数字个数的乘积。

int Bfs(int s) {
  vector<int> flag(10, 0);
  queue<int> q;
  q.push(s);
  flag[s] = 1;
  int ans = 1;
  while (!q.empty()) {
    int u = q.front();
    q.pop();
    for (int v : g[u]) {
      if (!flag[v]) {
        flag[v] = 1;
        ans++;
        q.push(v);
      }
    }
  }
  return ans;
}

由于乘积可能很大,所以需要使用高精度乘法或者 int128。
而使用 int128 时,需要自己实现一个 ToString 函数。

__int128 ans = 1;
string S = buf;
for (auto c : S) {
  __int128 cnt = nums[c - '0'];
  ans = ans * cnt;
}
printf("%s\n", toString(ans).c_str());

四、过河卒

题意:给一个网格,从左上角走到右下角,只能向右与向下走,问有多少种走法。
限制:有一个坐标有一个象棋的马,马一步能到达的地方不能走。

思路:动态规划

预处理:预处理哪些位置不能走。
状态定义:dp(x,y) 从左上角到达 (x,y)的路径数。

状态转移方程:

dp(x,y) = dp(x-1, y) + dp(x, y-1);

五、最后

这次比赛的四道题都不难。
第一题,枚举。
第二题,搜索与大素数判定。
第三题,搜索与高精度乘法。
第四题,网格动态规划。

《完》

-EOF-

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

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

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

tiankonguse +
穿越