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;
}
posted @ 2024-02-21 01:30  Cocoicobird  阅读(23)  评论(0编辑  收藏  举报