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的方案数 B
,A - B
即为答案。
- 首先求 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)
- 这个d不构成子序列 red 的一部分,即前 i - 1 个字母中存在 red 子序列,方案数为
- 若该字符串以 r 或者 e 结尾,则 red 子序列在前 i - 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)
]
- 这个 e 不构成子序列 re 的一部分,即前 i - 1 个字母中存在 re 子序列,方案数为
- 若该字符串以 r 结尾,则 re 子序列在前 i - 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]
- 不以 d 结尾,则前 i - 1 个字母中存在 red 子串,方案数 =
- 若该字符串以 red 结尾,则前 i - 3 个字符可任意排列,方案数 =
综上 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;
}