第7章 数学
1049. Counting Ones
笔记
- 可参考《算法基础课》数位统计DP的思路,这题只是那题的特例
#include <iostream>
#include <cmath>
#include <vector>
using namespace std;
const int N = 13; // 位数
int get(int l, int r, vector<int> num) {
int res = 0;
for (int i = l; i >= r; i--)
res = res * 10 + num[i];
return res;
}
int count (int n, int x) {
if (!n) return 0; // 特判
vector<int> num;
while(n) {
num.push_back(n % 10);
n /= 10;
}
n = num.size();
int res = 0;
for (int i = n - 1 - !x; i >= 0; i--) {
if (i < n - 1) {
// 左边存在
res += get(n - 1, i + 1, num) * pow(10, i);
if (!x) res -= pow(10, i); // 如果是计算0的位数,则不能让左边为全为0
}
if (x == num[i]) res += get(i - 1, 0, num) + 1;
else if (x < num[i]) res += pow(10, i);
}
return res;
}
int main() {
int n;
cin >> n;
cout << count(n, 1) << endl;
return 0;
}
1059. Prime Factors
笔记
- 当找到 n n n的质因子 a a a时,只需再从 n / a n / a n/a找下一个质因子,这样可把时间复杂度降到 O ( n ) O(\sqrt{n}) O(n)
- 注意
- 题目特判 1 1 1的情况
- 写成
i <= n / i
,防止乘法溢出 - 当循环结束后,
n
n
n仍然可能是一个质因数,仍需要考虑
- 如果它是,则一定是最大的质因数且只有一个
- 如果不是,它一定是 1 1 1
#include <iostream>
using namespace std;
string res;
void add(int x, int cnt) {
res += to_string(x);
if (cnt > 1) res += "^" + to_string(cnt);
res += "*";
}
int main() {
int n;
cin >> n;
if (n == 1) res = "1=1";
else {
res = to_string(n) + "=";
for (int i = 2; i <= n / i; i++)
if (n % i == 0) {
int cnt = 0;
while(n % i == 0) {
cnt++;
n /= i;
}
add(i, cnt);
}
if (n > 1) add(n, 1);
res.pop_back();
}
cout << res << endl;
return 0;
}
1081. Rational Sum
笔记
- 通分再计算,由于可能会产生乘法溢出,因此需要尽可能约分,即计算最大公约数后,分子和分母立即除以最大公约数
- 最大公约数可通过辗转相除法计算
- 假设 t = gcd ( b , d ) t = \gcd(b, d) t=gcd(b,d),则可以根据下式进行更深层的约分(否则过不了其中一个测试用例)
d × a + b × c b × d = d t × a + b t × c b t × d \frac{d \times a + b \times c}{b \times d}=\frac{ \frac{d}{t} \times a + \frac{b}{t} \times c}{ \frac{b}{t} \times d} b×dd×a+b×c=tb×dtd×a+tb×c
#include <iostream>
using namespace std;
typedef long long LL;
LL gcd(LL a, LL b) {
return b ? gcd(b, a % b) : a;
}
void f(LL& a, LL& b) {
// 约分
LL d = gcd(a, b);
a = a / d;
b = b / d;
}
int main() {
int n;
cin >> n;
LL a, b, as = 0, bs = 1;
while(n--) {
scanf("%lld/%lld", &a, &b);
f(a, b); // 约分
LL t = gcd(b, bs);
as = bs / t * a + b / t * as;
bs = b / t * bs;
f(as, bs); // 约分
}
if (as % bs == 0) printf("%lld\n", as / bs);
else if (as >= bs) printf("%lld %lld/%lld\n", as / bs, as % bs, bs);
else printf("%lld/%lld\n", as, bs);
return 0;
}
1088. Rational Arithmetic
笔记
- 根据通分公式计算即可,难点在于各种输出要求
#include <iostream>
using namespace std;
typedef long long LL;
LL gcd(LL a, LL b) {
return b ? gcd(b, a % b) : a;
}
void f(LL& a, LL& b) {
LL t = gcd(a, b);
a /= t;
b /= t;
}
string format(LL a, LL b) {
bool flag = (a < 0) ^ (b < 0);
a = abs(a);
b = abs(b);
string res;
if (a % b == 0) res = to_string(a / b);
else if (a > b) res = to_string(a / b) + ' ' + to_string(a % b) + '/' + to_string(b);
else res = to_string(a) + '/' + to_string(b);
if (flag) res = "(-" + res + ")";
return res;
}
string add(LL a, LL b, LL c, LL d, LL &p, LL& q) {
LL t = gcd(b, d);
p = b / t * c + d / t * a;
q = b / t * d;
f(p, q);
return format(p, q);
}
string sub(LL a, LL b, LL c, LL d, LL &p, LL& q) {
LL t = gcd(b, d);
p = d / t * a - b / t * c;
q = b / t * d;
f(p, q);
return format(p, q);
}
string mul(LL a, LL b, LL c, LL d, LL &p, LL& q) {
LL t = gcd(b, d);
p = a * c;
q = b * d;
f(p, q);
return format(p, q);
}
string div(LL a, LL b, LL c, LL d, LL &p, LL& q) {
if (b == 0 || c == 0) return "Inf";
LL t = gcd(b, d);
p = a * d;
q = b * c;
f(p, q);
return format(p, q);
}
void print(LL a, LL b, LL c, LL d) {
f(a, b); // 约分
f(c, d); // 约分
string x_str = format(a, b);
string y_str = format(c, d);
LL p, q; // 结果
cout << x_str + " + " + y_str + " = " + add(a, b, c, d, p, q) << endl;
cout << x_str + " - " + y_str + " = " + sub(a, b, c, d, p, q) << endl;
cout << x_str + " * " + y_str + " = " + mul(a, b, c, d, p, q) << endl;
cout << x_str + " / " + y_str + " = " + div(a, b, c, d, p, q) << endl;
}
int main() {
LL a, b, c, d;
scanf("%lld/%lld %lld/%lld", &a, &b, &c, &d);
print(a, b, c, d);
return 0;
}
1096. Consecutive Factors
笔记
- 穷举所有情况即可
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n;
cin >> n;
vector<int> res, num;
// 枚举所有质数
for (int i = 2; i <= n / i; i++)
if (n % i == 0) {
num.clear();
// 尝试以此为最小的因子构造
int m = n, j = i;
while(m % j == 0) {
num.push_back(j);
m /= j;
j++;
}
if (num.size() > res.size()) res = num;
}
if (res.empty()) res.push_back(n); // 自身是质数的情况
cout << res.size() << endl;
cout << res[0];
for (int i = 1; i < res.size(); i++) cout << '*' << res[i];
cout << endl;
return 0;
}
1103. Integer Factorization
当 m = 169 , n = 5 , p = 2 m=169,n=5,p=2 m=169,n=5,p=2时
物品 i i i | 体积 j j j | 重量 k k k | 价值 f f f |
---|---|---|---|
1 | 1 | 1 | 1 |
2 | 4 | 1 | 2 |
3 | 9 | 1 | 3 |
… | … | … | … |
13 | 169 | 1 | 13 |
笔记
- 本题可看做是“二维完全背包具体方案”问题,可参考《算法提高课》“完全背包问题”、“二维费用的背包问题”和“背包问题求具体方案”
- 问题建模
- 假设
t
t
t是要分解的整数,
n
n
n是要分解的个数,
p
p
p是幂,则可看做一种背包问题,记
N
=
⌊
m
1
p
⌋
N=\lfloor m^{\frac{1}{p}} \rfloor
N=⌊mp1⌋
- 一共有 N N N种物品 a 1 , a 2 , ⋯ , a N a_1,a_2,\cdots, a_N a1,a2,⋯,aN
- 第 i i i个物品 a i a_i ai的体积 v i = i p v_i=i^p vi=ip,重量 m i ≡ 1 m_i \equiv 1 mi≡1,价值 w i = i w_i=i wi=i
- 背包的体积为 t t t,最大承重量为 n n n
- 问在背包的体积和重量达到最大值的条件下,怎么装物品才能让背包的价值最大?
- 假设
t
t
t是要分解的整数,
n
n
n是要分解的个数,
p
p
p是幂,则可看做一种背包问题,记
N
=
⌊
m
1
p
⌋
N=\lfloor m^{\frac{1}{p}} \rfloor
N=⌊mp1⌋
- 已有背包问题模型
- “完全背包问题”:可重复选择
- “二维费用的背包问题”:体积、重量、价值
- “背包问题求具体方案”:需要确定怎么选
f[i][j][k] == f[i-1][j][k]
表示不选第 i i i个物品f[i][j][k] == f[i][j-v][k-1]+i
表示选第 i i i个物品
- 算法实现细节
- 为了判断是否有解,需要把
f
初始化为负数 - 最终解是
f[m][n][k]
- 为了判断是否有解,需要把
#define LOCAL
#include <iostream>
#include <cmath>
#include <cstring>
using namespace std;
const int N = 410, M = 21;
int f[M][N][N]; // 考虑前i个物品,体积恰好为j,重量恰好为k的最大价值
int main() {
int m, n, p;
cin >> m >> n >> p;
memset(f, 0xef, sizeof f); // 初始化为负数
f[0][0][0] = 0; // 什么都不选时价值为0
// 动态规划
int i = 1;
int v = pow(i, p);
while(v <= m) {
// 构造第i个物品,直到第i个物品的体积超出背包所能容纳的体积
for (int j = 0; j <= m; j++) // 遍历背包最大体积的所有可能情况(目标)
for (int k = 0; k <= n; k++) { // 遍历背包最大重量的所有可能情况(个数)
f[i][j][k] = f[i - 1][j][k]; // 默认不选
if (j >= v && k >= 1)
f[i][j][k] = max(f[i][j][k], f[i][j - v][k - 1] + i); // 状态转移
}
i++; // 注意这个语句必须在计算v之前,否则不对
v = pow(i, p);
}
int max_i = i - 1; // 物品总数
// 输出结果
if (f[max_i][m][n] < 0) puts("Impossible"); // 无解
else {
printf("%d = ", m);
string res;
for (int i = max_i; i > 0; i--) {
int v = pow(i, p);
while (m >= v && n >= 1 && f[i][m][n] == f[i][m - v][n - 1] + i) {
res += to_string(i) + '^' + to_string(p) + " + ";
m -= v;
n--;
}
}
cout << res.substr(0, res.size() - 3) << endl;
}
return 0;
}
1104. Sum of Number Segments
笔记
- 对于第
i
i
i个数,它在所有段求和中出现的次数为
i
×
(
n
−
i
+
1
)
i \times (n - i + 1)
i×(n−i+1)
- 考虑含有 a 1 a_1 a1的项,显然其出现的次数为 n n n
- 考虑含有
a
2
a_2
a2的项,可分为两部分,共出现
2
(
n
−
1
)
2(n-1)
2(n−1)次
- 有 a 1 a_1 a1且有 a 2 a_2 a2的项,出现次数为 n − 1 n-1 n−1
- 没有 a 1 a_1 a1但有 a 2 a_2 a2的项,出现次数也为 n − 1 n-1 n−1
- 考虑含有 a 3 a_3 a3的项,可分为三部分,同理共出现 3 ( n − 2 ) 3(n-2) 3(n−2)次
- 考虑含有 a i a_i ai的项,猜测共出现 i ( n − i + 1 ) i(n- i + 1) i(n−i+1)次
- 在计算时,先把
double
类型的写在前边可防止计算乘法时溢出 - 不能用
g++
编译,需要用更高级的clang++
编译才能通过
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
long double res, a;
for (int i = 1; i <= n; i++) {
cin >> a;
res += a * i * (n - i + 1); // 先写double类型的防止乘法溢出
}
printf("%.2Lf\n", res);
return 0;
}