AcWing寒假每日一题2024

好久没刷算法了,记录活动中一些印象深刻的题目。

1.[AcWing4662.因数平方和]

题目描述

f(x)x 的所有因数(约数)的平方的和。
例如:f(12)=12+22+32+42+62+122
定义 g(n)=i=1nf(i)
给定 n,求 g(n) 除以 109+7 的余数。

输入格式

输入一行包含一个正整数 n

输出格式

输出一个整数表示答案 g(n) 除以 109+7 的余数。

数据范围

对于 20% 的评测用例,n105
对于 30% 的评测用例,n107
对于所有评测用例,1n109

输入样例

100000

输出样例

680584257

解题思路

首先最简单的思路,针对每一个数,通过试除法获取其因数,求和,但是这样复杂度过高。
换个思路,针对每个数,其会对倍数在计算 f 时产生贡献,因此在 1n 中,对于任意一个数 i,其倍数个数为 ni,所以每个因子的贡献值为 i2ni,则 g(n)=i=1ni2ni,这样时间复杂度为 O(n),可以过 30% 的数据。
n=12,则 ini 的对应如下

1  2 3 4 5 6 7 8 9 10 11 12
12 6 4 3 2 2 1 1 1  1  1  1

可以看到,ni 是成块的,那么我们就可以进行区间的计算,找到每块的 lr 对应的 i,则这个区间的和可以通过平方的和公式进行计算,i=lri2ni=nii=lri2=ni(i=1ri2i=1l1i2)1n 的平方的和公式为 i=1ni2=n(n+1)(2n+1)6
对于 l 如何求 r?根据数据分块的结论,对于常数 n,使得式子 nl=nr 成立的最大的满足的 r 值(1rn) 为 nnl。并且因为数据范围利用求和公式计算时除以 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 个整数 w1,w2,,wn,依次表示每颗石子的重量。

输出格式

一个整数表示答案。

数据范围

1n105,
1wi1000

输入样例1

3
3 4 5

输出样例1

47

输入样例2

8
1 5 2 6 3 7 4 8

输出样例2

546

解题思路

最开始的思路是贪心,每次取最小的两个粘在一起,借助堆。
假设 a,b,c 三个石子,取两种排序方式,a,b,cb,c,a,则合并结果分别为ab+(a+b)c 以及 bc+(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,当它尝试从高度 i1 爬到高度为 i 的位置时有 Pi 的概率会掉回树根,求它从树根爬到树顶时,经过的时间的期望值是多少。

输入格式

输入第一行包含一个整数 n 表示树的高度。
接下来 n 行每行包含两个整数 xi,yi,用一个空格分隔,表示 Pi=xiyi

输出格式

输出一行包含一个整数表示答案,答案是一个有理数,请输出答案对质数 998244353 取模的结果。
其中有理数 ab 对质数 P 取模的结果是整数 c 满足 0c<Pcba(modP)

数据范围

对于 20% 的评测用例,n21xi<yi20
对于 50% 的评测用例,n5001xi<yi200
对于所有评测用例,1n1000001xi<yi109,为了保证不出现无解的情况,额外增加限制条件 yixi998244353(如不增加此条件,则可能出现无解情况,此为比赛原题考虑不周)。

输入样例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
当某位原本为 90 时可能会向前(左边)进位/退位,当最高位(左边第一位)上的数字变化时向前的进位或退位忽略。
例如:
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% 的评测用例,1n300
对于 60% 的评测用例,1n3000
对于所有评测用例,1n105x,y 中仅包含数字 09,可能有前导零。

输入样例

5
12349
54321

输出样例

11

解题思路

首先,这里的操作可以看作是十进制加减法。操作时有如下性质

  • 操作顺序不会影响操作结果
  • 对于某一位操作时,不会影响右边数位的结果
  • 对于某一位操作时往前一位最多进一位或者借一位

在最优解中,每一位操作次数区间为 [9,9],否则会产生进借位,不如直接操作被进或者借位

f[i][j] 表示 in 位相等,且对下一位进位状态是 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 @   Cocoicobird  阅读(39)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
历史上的今天:
2023-02-21 2023.2.21AcWing蓝桥杯集训·每日一题
点击右上角即可分享
微信分享提示