第七章 数学
AcWing 1533. 1 的个数
问题描述
-
问题链接:AcWing 1533. 1 的个数、原题链接
分析
-
这一题最直观的做法,就是枚举
1~n
中的每个数,然后将每个数中1
的个数加到res
中,最后返回res
。但是这种做法时间复杂度太高了。 -
另一种做法是枚举每一位上
1
的个数,从高位开始枚举,假设我们的数据是abcdefg
,如果我们现在枚举千位是1
有多少种方案,需要分类讨论:(1)如果
d==0
,则d
的左边可以取0~abc-1
,右边可以取0~999
,此时千位取1
对应的数据都小于原数,一共 a b c × 1000 abc \times 1000 abc×1000种方案;(2)如果
d==1
,同样左边可以取0~abc-1
,右边可以取0~999
,一共 a b c × 1000 abc \times 1000 abc×1000种方案;另外左边可以取abc
,右边可以取0~efg
,对应 e f g + 1 efg+1 efg+1种方案,因此一共 a b c × 1000 + e f g + 1 abc \times 1000 + efg + 1 abc×1000+efg+1种方案。(3)如果
d>1
,左边可以取0~abc
,右边可以取0~999
,一共 ( a b c + 1 ) × 1000 (abc + 1) \times 1000 (abc+1)×1000种方案。 -
下面的代码中用
left
记录上面演示的abc
,用right
记录上面演示的efg
。
代码
- C++
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
int n;
cin >> n;
vector<int> nums;
while (n) nums.push_back(n % 10), n /= 10;
reverse(nums.begin(), nums.end()); // 为了让nums[0]对应最高位
int res = 0;
for (int i = 0; i < nums.size(); i++) {
int d = nums[i];
int left = 0, right = 0, p = 1;
for (int j = 0; j < i; j++) left = left * 10 + nums[j];
for (int j = i + 1; j < nums.size(); j++) {
right = right * 10 + nums[j];
p *= 10;
}
if (d == 0) res += left * p;
else if (d == 1) res += left * p + right + 1;
else res += (left + 1) * p;
}
cout << res << endl;
return 0;
}
AcWing 1545. 质因子
问题描述
-
问题链接:AcWing 1545. 质因子、原题链接
分析
-
质因数分解,按照要求个数输出即可。
-
关于质数的内容可以参考:质数。
代码
- C++
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
if (n == 1) puts("1=1");
else {
printf("%d=", n);
bool is_first = true;
for (int i = 2; i <= n / i; i++)
if (n % i == 0) {
int s = 0;
while (n % i == 0) n /= i, s++;
if (is_first) is_first = false;
else printf("*");
printf("%d", i);
if (s > 1) printf("^%d", s);
}
if (n > 1) {
if (!is_first) printf("*");
printf("%d", n);
}
}
return 0;
}
AcWing 1567. 有理数的和
问题描述
-
问题链接:AcWing 1567. 有理数的和、原题链接
分析
- 按照分数的加法进行计算即可,如下:
b a + d c = b c + a d a c \frac{b}{a} + \frac{d}{c} = \frac{bc+ad}{ac} ab+cd=acbc+ad
- 计算的过程中要经常约分,这样计算才不会超出
long long
范围。上式计算的时候,要计算a、c
的最小公倍数,不然最后一个数据无法通过。
代码
- C++
#include <iostream>
using namespace std;
typedef long long LL;
LL gcd(LL a, LL b) {
return b ? gcd(b, a % b) : a;
}
int main() {
int n;
scanf("%d", &n);
LL b = 0, a = 1; // b / a
for (int i = 0; i < n; i++) {
LL c, d; // d / c
scanf("%lld/%lld", &d, &c);
LL t = gcd(c, d);
c /= t, d /= t;
t = gcd(a, c);
b = c / t * b + a / t * d;
a = a / t * c;
t = gcd(a, b);
a /= t, b /= t;
}
if (a == 1) printf("%d\n", b);
else {
if (b > a) printf("%d ", b / a), b %= a;
printf("%lld/%lld\n", b, a);
}
return 0;
}
AcWing 1578. 有理数运算
问题描述
-
问题链接:AcWing 1578. 有理数运算、原题链接
分析
- 按照加减乘除的运算法则进行计算即可。如下:
b a + d c = b c + a d a c b a − d c = b c − a d a c b a ∗ d c = b d a c b a / d c = a d b c \frac{b}{a} + \frac{d}{c} = \frac{bc+ad}{ac} \\ \frac{b}{a} - \frac{d}{c} = \frac{bc-ad}{ac} \\ \frac{b}{a} * \frac{d}{c} = \frac{bd}{ac} \\ \frac{b}{a} / \frac{d}{c} = \frac{ad}{bc} ab+cd=acbc+adab−cd=acbc−adab∗cd=acbdab/cd=bcad
- 为了防止溢出,使用
long long
存储数据。
代码
- C++
#include <iostream>
using namespace std;
typedef long long LL;
LL gcd(LL a, LL b) {
return b ? gcd(b, a % b) : a;
}
void print(LL a, LL b) {
LL t = gcd(a, b);
a /= t, b /= t;
if (a < 0) a *= -1, b *= -1;
bool is_minus = b < 0;
if (is_minus) cout << '(';
if (a == 1) cout << b;
else {
if (abs(b) > a) printf("%lld ", b / a), b = abs(b) % a;
printf("%lld/%lld", b, a);
}
if (is_minus) cout << ')';
}
void add(LL a, LL b, LL c, LL d) {
print(a, b), cout << " + ", print(c, d), cout << " = ";
b = b * c + a * d;
a *= c;
print(a, b), cout << endl;
}
void sub(LL a, LL b, LL c, LL d) {
print(a, b), cout << " - ", print(c, d), cout << " = ";
b = b * c - a * d;
a *= c;
print(a, b), cout << endl;
}
void mul(LL a, LL b, LL c, LL d) {
print(a, b), cout << " * ", print(c, d), cout << " = ";
b *= d;
a *= c;
print(a, b), cout << endl;
}
void div(LL a, LL b, LL c, LL d) {
print(a, b), cout << " / ", print(c, d), cout << " = ";
if (!d) puts("Inf");
else {
b *= c;
a *= d;
print(a, b), cout << endl;
}
}
int main() {
// b/a ? d/c
LL a, b, c, d;
scanf("%lld/%lld %lld/%lld", &b, &a, &d, &c);
add(a, b, c, d);
sub(a, b, c, d);
mul(a, b, c, d);
div(a, b, c, d);
return 0;
}
AcWing 1586. 连续因子
问题描述
-
问题链接:AcWing 1586. 连续因子、原题链接
分析
- 我们枚举最长的连续因子的起点即可,然后找到以该点为起点的最长的一段,最后用这段更新答案即可。
代码
- C++
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n;
cin >> n;
vector<int> res;
for (int i = 2; i <= n / i; i++)
if (n % i == 0) {
vector<int> seq;
for (int j = i, s = n; s % j == 0; j++) {
seq.push_back(j);
s /= j;
}
if (seq.size() > res.size()) res = seq;
}
if (res.empty()) printf("1\n%d", n);
else {
cout << res.size() << endl;
cout << res[0];
for (int i = 1; i < res.size(); i++) cout << "*" << res[i];
}
cout << endl;
return 0;
}
AcWing 1593. 整数分解
问题描述
-
问题链接:AcWing 1593. 整数分解、原题链接
分析
-
将
N、K
看成背包的两个容量,对于每个数i
( 1 ≤ i ≤ 20 1 \le i \le 20 1≤i≤20,因为N
最大为400
)将其看成一个物品,其占用的两个容量分别为 i P 、 1 i^P、1 iP、1,价值是i
,求恰好将背包的两个容量装满获得的最大价值是多少? -
因此问题就转变为了一个二维费用背包问题(结合完全背包问题,因为每个物品可以使用多次)。关于背包问题请参考:背包问题(背包九讲)。
-
分析如下:
-
f(i, j, k)
是上述所有选择的最大值,同样和完全背包一样,可以对状态转移进行优化: -
f ( i , j , k ) = m a x ( f ( i − 1 , j , k ) , f ( i , j − i P , k − 1 ) + i ) f(i, j, k) = max(f(i-1, j, k), f(i, j - i^P, k-1) + i) f(i,j,k)=max(f(i−1,j,k),f(i,j−iP,k−1)+i)。
-
对于输出方案,如果存在多种方案,我们需要输出字典序更大的方案,因为我们从小到大考虑每个物品的,因此最后直接反推得到方案即可。
代码
- C++
#include <iostream>
#include <cstring>
using namespace std;
const int N = 410;
int n, K, p;
int f[21][N][N];
// 返回a^b
int pow(int a, int b) {
int res = 1;
for (int i = 0; i < b; i++) res *= a;
return res;
}
int main() {
cin >> n >> K >> p;
memset(f, -0x3f, sizeof f);
f[0][0][0] = 0;
int m = 0; // 记录考虑到的物品数
for (int i = 1; ; i++) {
int v = pow(i, p); // 物品的体积
if (v > n) {
m = i - 1;
break;
}
for (int j = 0; j <= n; j++)
for (int k = 0; k <= K; k++) {
f[i][j][k] = f[i - 1][j][k];
if (j >= v && k) f[i][j][k] = max(f[i][j][k], f[i][j - v][k - 1] + i);
}
}
if (f[m][n][K] < 0) puts("Impossible");
else {
printf("%d = ", n);
bool is_first = true;
while (m) {
int v = pow(m, p);
while (n >= v && K && f[m][n - v][K - 1] + m == f[m][n][K]) {
if (is_first) is_first = false;
else printf(" + ");
printf("%d^%d", m, p);
n -= v, K -= 1;
}
m--;
}
}
return 0;
}
AcWing 1594. 数段之和
问题描述
-
问题链接:AcWing 1594. 数段之和、原题链接
分析
-
考虑每个数据会在多少个区间出现,假设有
n
个数据,则考虑第i
个数据在多少个区间出现:以第i
个数据为结尾的区间有i
个,以第i
个数据为起点的区间有n - i + 1
个,因此根据乘法原理,第i
个数据一共会在i*(n-i+1)
个区间出现。 -
注意,为了达到精度,这里需要使用
long double
。
代码
- C++
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
long double res = 0;
for (int i = 1; i <= n; i++) {
double x;
cin >> x;
res += x * i * (n - i + 1);
}
printf("%.2Lf\n", res);
return 0;
}
AcWing 1602. 卡住的键盘
问题描述
-
问题链接:AcWing 1602. 卡住的键盘、原题链接
分析
-
使用
st
数组记录哪些字符是正常的,如果一个字符是正常的,则记为1
,不正常的记为0
。 -
最后根据
st
数组进行输出。
代码
- C++
#include <iostream>
using namespace std;
const int N = 300;
// 每个字符的状态
// 0: 可能损坏的按键,但未输出; 1: 正常按键; 2: 可能损坏的按键,并且已经输出
int st[N];
int main() {
int k;
string s;
cin >> k >> s;
for (int i = 0; i < s.size(); i++) {
int j = i + 1;
while (j < s.size() && s[j] == s[i]) j++;
int cnt = j - i;
if (cnt % k) st[s[i]] = 1;
i = j - 1;
}
string res;
for (int i = 0; i < s.size(); i++) {
if (st[s[i]] == 0) cout << s[i], st[s[i]] = 2;
if (st[s[i]] == 1) res += s[i];
else {
res += s[i];
i += k - 1;
}
}
cout << endl << res << endl;
return 0;
}
AcWing 1606. C 语言竞赛
问题描述
-
问题链接:AcWing 1606. C 语言竞赛、原题链接
分析
-
本题相当于让我们判断一个数是质数还是合数。可以使用埃式筛法求解。
-
关于质数内容可以参考:质数。
代码
- C++
#include <iostream>
using namespace std;
const int N = 10010;
int n;
int rnk[N]; // 每个人对应的名次
// st[i]==0: 表示i是1; st[i]==1: 表示i是质数; st[i]==2: 表示i是合数
int st[N];
// 埃式筛法
void init() {
for (int i = 2; i < N; i++)
if (!st[i]) {
st[i] = 1;
for (int j = i + i; j < N; j += i)
st[j] = 2;
}
}
int main() {
init();
cin >> n;
for (int i = 1; i <= n; i++) {
int id;
cin >> id;
rnk[id] = i; // id排名是i
}
int k;
cin >> k;
while (k--) {
int x;
cin >> x;
printf("%04d: ", x);
if (!rnk[x]) // 不存在x
printf("Are you kidding?\n");
else if (rnk[x] == -1) // 存在x,但是已经查过了
printf("Checked\n");
else {
if (st[rnk[x]] == 0) printf("Mystery Award\n"); // 第一名
else if (st[rnk[x]] == 1) printf("Minion\n"); // 质数
else printf("Chocolate\n"); // 合数
rnk[x] = -1;
}
}
return 0;
}
AcWing 1646. 谷歌的招聘
问题描述
-
问题链接:AcWing 1646. 谷歌的招聘、原题链接
分析
-
本题的思路就是遍历所有长度为
K
的连续数字,然后判断其是否为质数,然后输出第一个质数即可,如果都不是质数,输出404
即可。 -
数据最大为
9
位,即最大为 1 0 9 − 1 10^9 - 1 109−1,判断质数的时间复杂度是 O ( n ) O(\sqrt{n}) O(n)的,需要30000
的计算量,最多判断1000
个,需要三千万的计算量,可能超时,因此需要优化。 -
对于判断质数进行优化,我们只使用质因子进行试除法判断质数,这样判断质数的时间复杂度是是 O ( n l o g ( n ) ) O(\frac{\sqrt{n}}{log(\sqrt{n})}) O(log(n)n)的,最终大约需要三百万的计算量,可行。
代码
- C++
#include <iostream>
#include <cstring>
using namespace std;
const int N = 40000; // N^2 = 1.6*10^9, 足够
int n, k;
bool st[N];
int prime[N], cnt;
// 线性筛法
void init() {
for (int i = 2; i < N; i++) {
if (!st[i]) prime[cnt++] = i;
for (int j = 0; prime[j] < N / i; j++) {
st[prime[j] * i] = true;
if (i % prime[j] == 0) break;
}
}
}
bool check(int x) {
for (int i = 0; prime[i] <= x / prime[i]; i++)
if (x % prime[i] == 0)
return false;
return true;
}
int main() {
init();
string str;
cin >> n >> k >> str;
for (int i = 0; i + k <= str.size(); i++) {
int t = stoi(str.substr(i, k));
if (check(t)) {
cout << str.substr(i, k) << endl;
return 0;
}
}
puts("404");
return 0;
}