[算法题] 模拟笔试 - 卡码网周赛第二十四期

138. 完美数

题解

在这里插入图片描述
这道题如果暴力搜索,复杂度是 O ( n 2 ) O(n^2) O(n2),显然不太靠谱

利用哈希表将复杂度降为 O ( n ) O(n) O(n),由于1 <= a[i] <= 10^9,因此2个数的乘积介于1 <= a[i] <= 10^18之间,可以先生成所有的完美数集合perfect_numbers,针对数组中的每个数num,查询 perfect_number / num

#include <iostream>
#include <vector>
#include <unordered_map>
#include <unordered_set>

using namespace std;

void GenPerfectNumbers(unordered_set<unsigned long long>& perfect_numbers) {
    unsigned long long upper = 1e18;
    for (unsigned long long i = 1; i < upper; i *= 10) {
        for (unsigned long long j = 1; j <= 9; ++j) {
            perfect_numbers.insert(i * j);
        }
    }
    perfect_numbers.insert(upper);
}

int main() {
    size_t n;
    cin >> n;
    vector<unsigned long long> a(n);
    unordered_map<unsigned long long, int> count_map;
    for (size_t i = 0; i < n; ++i) {
        cin >> a[i];
        ++count_map[a[i]];
    }

    unordered_set<unsigned long long> perfect_numbers;
    GenPerfectNumbers(perfect_numbers);
    
    int count = 0;
    for (size_t i = 0; i < n; ++i) {
        --count_map[a[i]];
        for (auto& p : perfect_numbers) {
            if (p % a[i] == 0) {
                unsigned long long other = p / a[i];
                if (count_map.find(other) != count_map.end() && count_map[other] > 0) {
                    count += count_map[other];
                }
            }
        }
    }
    cout << count << endl;
    return 0;
}

注意

  • 这种根据一个数找另一个数的题基本都要使用哈希表

139. 可爱串

题解

在这里插入图片描述

这题不会,参考 可爱串

设法求得所有包含子序列red(可连续)的方案数 A包含子串red的方案数 BA - B 即为答案。

  1. 首先求 A:
  • 假设 f(i) 表示长度为 i 的字符串中包含 red 子序列的方案数。f(i) = f(i-1) * 3 + g(i-1)
    • 若该字符串以 r 或者 e 结尾,则 red 子序列在前 i - 1 个字母中,方案数为 f(i-1) * 2
    • 若该字符串以 d 结尾,可分为以下两种情况
      • 这个d不构成子序列 red 的一部分,即前 i - 1 个字母中存在 red 子序列,方案数为f(i-1)
      • 这个 d 必须构成子序列 red 的一部分,即前 i - 1 个字母中只有re子序列,没有 red,设其为g(i-1)
  1. 所以现在需要求 g(i),也即长度为 i 的字符串中包含 re 子序列但不包含 red 的方案数
  • g(i) 表示长度为 i 的字符串中包含 re 子序列的方案数。g(i) = g(i-1) * 2 + (i - 1) * 2^(i - 2)
    • 若该字符串以 r 结尾,则 re 子序列在前 i - 1 个字母中,方案数为 g(i-1)
    • 若该字符串以 e 结尾,可分为以下两种情况
      • 这个 e 不构成子序列 re 的一部分,即前 i - 1 个字母中存在 re 子序列,方案数为 g(i-1)
      • 这个 e 必须构成子序列 re 的一部分,即前 i - 1 个字母中不能存在e在r后面这种情况,因此等于 (i - 1) * 2^(i - 2) [(i-1)是指r可以取i-1种可能,一旦选定r,其左侧只能是e或者d,右侧只能是r或者d,为 2^(i - 2)]
  1. 求 B:
  • h(i) 表示长度为 i 的字符串中包含 red 子串的方案数。h(i) = 3 ^(i - 3) + h(i-1) * 3 - h(i-3)
    • 若该字符串以 red 结尾,则前 i - 3 个字符可任意排列,方案数 =3 ^(i - 3)
    • 若该字符串不以 red 结尾,可分为以下两种情况
      • 不以 d 结尾,则前 i - 1 个字母中存在 red 子串,方案数 = h(i-1) * 2 [乘2是因为最后一位可能是r或者e]
      • 以 d 结尾,则前 i - 1 个字母中存在 red 子串 并且倒数两位不能是 re。所以方案数是 h(i-1) - h(i-3) [也即 前 i - 1 个字母中存在 red 子串 - 前 i - 3 个字母中存在 red 子串 且倒数两位是 re]

综上 A - B:f(n) - h(n)。

#include <iostream>
#include <vector>
using namespace std;

const int MOD = 1e9 + 7;

long long QuickPow(long long base, int exponent) {
    long long result = 1;
    while (exponent > 0) {
        if (exponent & 1) {
            result = (result * base) % MOD;
        }
        base = (base * base) % MOD;
        exponent >>= 1;
    }
    return result;
}

int main() {
    int n;
    cin >> n;
    vector<long long> f(n + 1, 0), g(n + 1, 0), h(n + 1, 0);
    
    g[2] = 1;
    for (int i = 3; i <= n; ++i) {
        g[i] = ((g[i - 1] * 2) % MOD + ((i - 1) * QuickPow(2, i - 2)) % MOD) % MOD;
    }
    
    f[3] = 1;
    for (int i = 4; i <= n; ++i) {
        f[i] = ((f[i - 1] * 3) % MOD + g[i - 1]) % MOD;
    }
    
    h[3] = 1;
    for (int i = 4; i <= n; ++i) {
        h[i] = (QuickPow(3, i - 3) + (h[i - 1] * 3) % MOD - h[i - 3] + MOD) % MOD;
    }
    
    cout << (f[n] - h[n] + MOD) % MOD << endl;
    return 0;
}

注意

  • 动态规划情况比较多,需要慢慢分析
  • MOD需要注意
    (a + b) % p = (a % p + b % p) % p
    (a - b) % p = (a % p - b % p + p) % p 注意不要忘了加p,防止变成负值
    (a * b) % p = (a % p * b % p) % p
  • 快速幂的原理示例:
    在这里插入图片描述

140. 好二叉树

题解

在这里插入图片描述
好二叉树的左子树和右子树都是好二叉树
动态规划:dp[n] = dp[1] * dp[n - 2] + dp[3] * dp[n - 4] + ... + dp[n - 2] * dp[1]

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    size_t n; 
    cin >> n;
    if (n == 1 || n % 2 == 0) {
        cout << 0 << endl;
        return 0;
    }
    std::vector<unsigned long long>dp(n + 1, 0);
    const unsigned long long MOD = 1e9 + 7;
    dp[1] = 1;
    for (size_t i = 3; i <= n; i += 2) {
        for (size_t j = 1; j <= i - 2; j += 2) {
            dp[i] = (dp[i] + (dp[j] * dp[i - 1 - j]) % MOD) % MOD;
        }
    }
    std::cout << dp[n] <<  std::endl;
    return 0;
}
  • 10
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值