AcWing寒假每日一题2024
好久没刷算法了,记录活动中一些印象深刻的题目。
1.[AcWing4662.因数平方和]
题目描述
记 \(f(x)\) 为 \(x\) 的所有因数(约数)的平方的和。
例如:\(f(12)=1^2+2^2+3^2+4^2+6^2+12^2\)。
定义 \(g(n)=\sum \limits_{i=1}^nf(i)\)。
给定 \(n\),求 \(g(n)\) 除以 \(10^9+7\) 的余数。
输入格式
输入一行包含一个正整数 \(n\)。
输出格式
输出一个整数表示答案 \(g(n)\) 除以 \(10^9+7\) 的余数。
数据范围
对于 \(20\%\) 的评测用例,\(n≤10^5\)。
对于 \(30\%\) 的评测用例,\(n≤10^7\)。
对于所有评测用例,\(1≤n≤10^9\)。
输入样例
100000
输出样例
680584257
解题思路
首先最简单的思路,针对每一个数,通过试除法获取其因数,求和,但是这样复杂度过高。
换个思路,针对每个数,其会对倍数在计算 \(f\) 时产生贡献,因此在 \(1\) 至 \(n\) 中,对于任意一个数 \(i\),其倍数个数为 \(\lfloor \frac{n}{i} \rfloor\),所以每个因子的贡献值为 \(i^2 * \lfloor \frac{n}{i} \rfloor\),则 \(g(n)=\sum\limits_{i=1}^n i^2*\lfloor \frac{n}{i} \rfloor\),这样时间复杂度为 \(O(n)\),可以过 \(30\%\) 的数据。
当 \(n=12\),则 \(i\) 和 \(\lfloor \frac{n}{i} \rfloor\) 的对应如下
1 2 3 4 5 6 7 8 9 10 11 12
12 6 4 3 2 2 1 1 1 1 1 1
可以看到,\(\lfloor \frac{n}{i} \rfloor\) 是成块的,那么我们就可以进行区间的计算,找到每块的 \(l\) 和 \(r\) 对应的 \(i\),则这个区间的和可以通过平方的和公式进行计算,\(\sum\limits_{i=l}^r i^2 * \lfloor \frac{n}{i} \rfloor=\lfloor \frac{n}{i} \rfloor \sum\limits_{i=l}^ri^2=\lfloor \frac{n}{i} \rfloor(\sum\limits_{i=1}^{r}i^2-\sum\limits_{i=1}^{l-1}i^2)\),\(1\) 至 \(n\) 的平方的和公式为 \(\sum\limits_{i=1}^ni^2=\frac{n(n+1)(2n+1)}{6}\)。
对于 \(l\) 如何求 \(r\)?根据数据分块的结论,对于常数 \(n\),使得式子 \(\lfloor \frac{n}{l} \rfloor=\lfloor \frac{n}{r} \rfloor\) 成立的最大的满足的 \(r\) 值(\(1≤r≤n\)) 为 \(\lfloor \frac{n}{\lfloor \frac{n}{l} \rfloor} \rfloor\)。并且因为数据范围利用求和公式计算时除以 \(6\) 采用乘逆元的形式来应对取模。
C++代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MOD = 1e9 + 7;
int n;
LL cal(int n) {
return n * (__int128)(n + 1) * (2 * n + 1) / 6 % MOD;
}
int main() {
cin >> n;
int res = 0;
for (int i = 1; i <= n; ) {
int x = n / i, y = n / x;
res = (res + (cal(y) - cal(i - 1)) * x) % MOD;
i = y + 1;
}
cout << (res + MOD) % MOD;
return 0;
}
2.[AcWing2875.超级胶水]
题目描述
小明有 \(n\) 颗石子,按顺序摆成一排。
他准备用胶水将这些石子粘在一起。
每颗石子有自己的重量,如果将两颗石子粘在一起,将合并成一颗新的石子,重量是这两颗石子的重量之和。
为了保证石子粘贴牢固,粘贴两颗石子所需要的胶水与两颗石子的重量乘积成正比,本题不考虑物理单位,认为所需要的胶水在数值上等于两颗石子重量的乘积。
每次合并,小明只能合并位置相邻的两颗石子,并将合并出的新石子放在原来的位置。
现在,小明想用最少的胶水将所有石子粘在一起,请帮助小明计算最少需要多少胶水。
输入格式
输入的第一行包含一个整数 \(n\),表示初始时的石子数量。
第二行包含 \(n\) 个整数 \(w_1,w_2,…,w_n\),依次表示每颗石子的重量。
输出格式
一个整数表示答案。
数据范围
\(1≤n≤10^5,\)
\(1≤w_i≤1000\)
输入样例1
3
3 4 5
输出样例1
47
输入样例2
8
1 5 2 6 3 7 4 8
输出样例2
546
解题思路
最开始的思路是贪心,每次取最小的两个粘在一起,借助堆。
假设 \(a,b,c\) 三个石子,取两种排序方式,\(a,b,c\) 和 \(b,c,a\),则合并结果分别为\(a * b + (a + b) * c\) 以及 \(b * c + (b + c) * a\),展开是相同的,扩展仍然成立,故直接模拟前面的计算过程即可。
C++代码
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
typedef long long LL;
int n, w[N];
priority_queue<int, vector<int>, greater<int>> q;
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", &w[i]);
LL ans = 0, sum = 0;
/*
for (int i = 1; i <= n; i++) q.push(w[i]);
while (q.size() > 1) {
int a = q.top(); q.pop();
int b = q.top(); q.pop();
ans += ((LL) a * b);
q.push(a + b);
}
*/
for (int i = 1; i <= n; i++) {
ans += sum * w[i];
sum += w[i];
}
printf("%lld", ans);
return 0;
}
3.[AcWing4646.爬树的甲壳虫]
题目描述
有一只甲壳虫想要爬上一棵高度为 \(n\) 的树,它一开始位于树根,高度为 \(0\),当它尝试从高度 \(i−1\) 爬到高度为 \(i\) 的位置时有 \(P_i\) 的概率会掉回树根,求它从树根爬到树顶时,经过的时间的期望值是多少。
输入格式
输入第一行包含一个整数 \(n\) 表示树的高度。
接下来 \(n\) 行每行包含两个整数 \(x_i,y_i\),用一个空格分隔,表示 \(P_i=x_iy_i\)。
输出格式
输出一行包含一个整数表示答案,答案是一个有理数,请输出答案对质数 \(998244353\) 取模的结果。
其中有理数 \(ab\) 对质数 \(P\) 取模的结果是整数 \(c\) 满足 \(0≤c<P\) 且 \(c⋅b≡a(modP)\)。
数据范围
对于 \(20\%\) 的评测用例,\(n≤2\),\(1≤x_i<y_i≤20\);
对于 \(50\%\) 的评测用例,\(n≤500\),\(1≤x_i<y_i≤200\);
对于所有评测用例,\(1≤n≤100000\),\(1≤x_i<y_i≤10^9\),为了保证不出现无解的情况,额外增加限制条件 \(y_i−x_i≠998244353\)(如不增加此条件,则可能出现无解情况,此为比赛原题考虑不周)。
输入样例1
1
1 2
输出样例1
2
输入样例2
3
1 2
3 5
7 11
输出样例2
623902744
解题思路
C++代码
#include <bits/stdc++.h>
using namespace std;
const int P = 998244353;
typedef long long LL;
int n;
LL qmi(int a, int b) {
LL res = 1;
while (b) {
if (b & 1)
res = res * a % P;
a = (LL) a * a % P;
b >>=1;
}
return res;
}
int main() {
scanf("%d", &n);
LL res = 0;
while (n--) {
int x, y;
scanf("%d%d", &x, &y);
res = (res + 1ll) * y % P * qmi(y - x, P - 2) % P;
}
printf("%lld\n", res);
return 0;
}
4.[AcWing5408.保险箱]
题目描述
小蓝有一个保险箱,保险箱上共有 \(n\) 位数字。
小蓝可以任意调整保险箱上的每个数字,每一次操作可以将其中一位增加 \(1\) 或减少 \(1\)。
当某位原本为 \(9\) 或 \(0\) 时可能会向前(左边)进位/退位,当最高位(左边第一位)上的数字变化时向前的进位或退位忽略。
例如:
\(00000\) 的第 \(5\) 位减 \(1\) 变为 \(99999\);
\(99999\) 的第 \(5\) 位减 \(1\) 变为 \(99998\);
\(00000\) 的第 \(4\) 位减 \(1\) 变为 \(99990\);
\(97993\) 的第 \(4\) 位加 \(1\) 变为 \(98003\);
\(99909\) 的第 \(3\) 位加 \(1\) 变为 \(00009\)。
保险箱上一开始有一个数字 \(x\),小蓝希望把它变成 \(y\),这样才能打开它,问小蓝最少需要操作的次数。
输入格式
输入的第一行包含一个整数 \(n\)。
第二行包含一个 \(n\) 位整数 \(x\)。
第三行包含一个 \(n\) 位整数 \(y\)。
输出格式
输出一行包含一个整数表示答案。
数据范围
对于 \(30\%\) 的评测用例,\(1≤n≤300\);
对于 \(60\%\) 的评测用例,\(1≤n≤3000\);
对于所有评测用例,\(1≤n≤10^5\),\(x,y\) 中仅包含数字 \(0\) 至 \(9\),可能有前导零。
输入样例
5
12349
54321
输出样例
11
解题思路
首先,这里的操作可以看作是十进制加减法。操作时有如下性质
- 操作顺序不会影响操作结果
- 对于某一位操作时,不会影响右边数位的结果
- 对于某一位操作时往前一位最多进一位或者借一位
在最优解中,每一位操作次数区间为 \([-9,9]\),否则会产生进借位,不如直接操作被进或者借位
\(f[i][j]\) 表示 \(i\) 到 \(n\) 位相等,且对下一位进位状态是 \(j\) 的所有方案集合。
C++代码
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int n;
string a, b;
int f[N][3];
int main() {
cin >> n >> a >> b;
memset(f, 0x3f, sizeof f);
f[n][1] = 0;
for (int i = n - 1; i >= 0; i--)
for (int j = 0; j < 3; j++)
for (int k = -9; k <= 9; k++)
for (int t = 0; t < 3; t++)
if (a[i] + k + t - 1 - b[i] == (j - 1) * 10)
f[i][j] = min(f[i][j], f[i + 1][t] + abs(k));
printf("%d\n", min({f[0][0], f[0][1], f[0][2]}));
return 0;
}
本文来自博客园,作者:Cocoicobird,转载请注明原文链接:https://www.cnblogs.com/Cocoicobird/p/18000364