目录
引言
这几道题主要都是考察数学,如果数学学的不好,推不出来公式,或者是你的数学思维不好都是做不出来的,所以说数学真的很重要,不过其实能考的数学也就那几个,无非就是很多而已,把常见的题型会了就行了,加油!
一、因数平方和
标签:数学、贡献法、整数划分
思路:
如果是单一求每一个 f ( x ) f(x) f(x) 那么即使每一个是 O ( l o g N ) O(logN) O(logN)的时间复杂度,那么也要 O ( N l o g N ) O(NlogN) O(NlogN),而 n < = 1 0 9 n<=10^9 n<=109明显已经超时了,所以可以换一种思路。用贡献法,那么每个约数 i i i 对结果的贡献应该是 ∑ i = 1 n n i ⋅ i 2 \sum_{i=1}^{n} \frac{n}{i}\cdot i^2 ∑i=1nin⋅i2 ,而最多只可能有 2 n 2\sqrt{n} 2n 个不同的约数,而这个可以用整数划分来做,把每一个 n i \frac{n}{i} in相等的数划分到一块,这样就可以根据平方和公式来 i 2 i^2 i2了,然后可以用二分或者公式求出 n y = x \frac{n}{y}=x yn=x的最大的y,也就是 n x \frac{n}{x} xn ,最后处理一下 S n S_n Sn 即可,可用逆元或者__int128来处理。 平方和公式: S n = n ⋅ ( n + 1 ) ⋅ ( 2 n + 1 ) 6 平方和公式:S_n = \frac{n\cdot(n+1)\cdot(2n+1)}{6} 平方和公式:Sn=6n⋅(n+1)⋅(2n+1)
题目描述:
记 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% 的评测用例,n≤105。
对于 30% 的评测用例,n≤107。
对于所有评测用例,1≤n≤109。
输入样例:
100000
输出样例:
680584257
示例代码一:__int128写法 (1e38)
cpp
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int MOD = 1e9+7;
int n;
LL calc(int n)
{
return n * (__int128)(n + 1) * (2 * n + 1) / 6 % MOD;
}
int main()
{
cin >> n;
LL res = 0;
for(int i = 1; i <= n;)
{
int x = n / i, y = n / x;
res = (res + (calc(y) - calc(i-1)) * x) % MOD;
i = y + 1;
}
cout << (res + MOD) % MOD << endl;
return 0;
}
示例代码二:逆元写法
cpp
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int MOD = 1e9+7;
int n;
//这样求也可以,前提是6与MOD是互质的
// LL qmi(LL a, LL k, int p)
// {
// LL res = 1;
// while(k)
// {
// if(k & 1) res = res * a % p;
// k >>= 1;
// a = a * a % p;
// }
// return res;
// }
// LL calc(int n)
// {
// return n * (n + 1ll) % MOD * (2 * n + 1) % MOD * qmi(6,MOD-2,MOD) % MOD;
// }
LL calc(int n)
{
return n * (n + 1ll) % MOD * (2 * n + 1) % MOD * 166666668 % MOD;
}
int main()
{
//逆元就是除上一个数取模和乘上一个数取模的结果一样
// for(int i = 1; ; ++i)
// {
// if(i * 6 % MOD == 1)
// {
// cout << i << endl;
// return 0;
// }
// }
cin >> n;
LL res = 0;
for(int i = 1; i <= n;)
{
int x = n / i, y = n / x;
res = (res + (calc(y) - calc(i-1)) * x) % MOD;
i = y + 1;
}
cout << (res + MOD) % MOD << endl;
return 0;
}
二、爬树的甲壳虫
标签:数学、概率论、数学期望、逆元、快速幂
思路:
这道题就是推公式,公式就不推了,说一下最后的结论: r e s = ( r e s + 1 ) ∗ y ∗ ( y − x ) − 1 res = (res+1) * y * (y-x)^{-1} res=(res+1)∗y∗(y−x)−1
题目描述:
示例代码:
cpp
#include <cstdio>
#include <iostream>
using namespace std;
typedef long long LL;
const int MOD = 998244353;
int n;
LL qmi(LL a, LL k, int p)
{
LL res = 1;
while(k)
{
if(k & 1) res = res * a % p;
k >>= 1;
a = a * a % p;
}
return res;
}
int main()
{
scanf("%d", &n);
LL res = 0;
while(n--)
{
int x, y;
scanf("%d%d", &x, &y);
res = (res + 1) * y % MOD * qmi(y-x, MOD-2, MOD) % MOD;
}
cout << res << endl;
return 0;
}
三、改变数组元素
标签:差分
思路:
这道题用暴力做的话就能过 8 / 10 8/10 8/10 的样子,然后再细看这道题就是对一段下标操作,如果操作过就是 1 1 1 ,没有操作过就是 0 0 0 ,那么可以用差分的思想,对于操作过的一段,可以直接加一,代表操作的次数,最后如果操作的次数不是 0 0 0 ,那么就输出 1 1 1 就行了。
题目描述:
cpp
给定一个空数组 V 和一个整数数组 a1,a2,...,an。
现在要对数组 V 进行 n 次操作。
第 i 次操作的具体流程如下:
从数组 V 尾部插入整数 0。
将位于数组 V 末尾的 ai 个元素都变为 1(已经是 1 的不予理会)。
注意:
ai 可能为 0,即不做任何改变。
ai 可能大于目前数组 V 所包含的元素个数,此时视为将数组内所有元素变为 1。
请你输出所有操作完成后的数组 V。
输入格式
第一行包含整数 T,表示共有 T 组测试数据。
每组数据第一行包含整数 n。
第二行包含 n 个整数 a1,a2,...,an。
输出格式
每组数据输出一行结果,表示所有操作完成后的数组 V,数组内元素之间用空格隔开。
数据范围
1≤T≤20000,1≤n≤2×105,0≤ai≤n,保证一个测试点内所有 n 的和不超过 2×105。
输入样例:
3
6
0 3 0 0 1 3
10
0 0 0 1 0 5 0 0 0 2
3
0 0 0
输出样例:
1 1 0 1 1 1
0 1 1 1 1 1 0 0 1 1
0 0 0
示例代码:
cpp
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5+10;
int T, n;
int b[N];
void insert(int l, int r)
{
b[l]++, b[r+1]--;
}
int main()
{
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin >> T;
while(T--)
{
cin >> n;
memset(b, 0, sizeof b);
for(int i = 1; i <= n; ++i)
{
int t;
cin >> t;
if(t == 0) continue;
else if(t > i)
{
insert(1, i);
}
else
{
insert(i-t+1,i);
}
}
for(int i = 1; i <= n; ++i)
{
b[i] += b[i-1];
if(b[i]) cout << 1 << " ";
else cout << 0 << " ";
}
cout << endl;
}
return 0;
}