目录
- Dirichlet前缀和
-
- 概述
- 代码
- 例题
- Dirichlet后缀和
-
- 概述
- 代码
- 例题
- 未完待续
Dirichlet前缀和
概述
狄利克雷前缀和是用来求解这样的问题,给定 { a n } \{a_n\} { an?},求解 { b n } \{b_n\} { bn?},使得 b k = ∑ d ∣ k a d b_k=\sum_{d|k}a_d bk?=d∣k∑?ad?
代码
例题处分析
for(int i=1;i<=CntPrime;i++)for(int j=1;j*Prime[i]<=N;j++)B[ j*Prime[i] ]+=B[j];
例题
https://www.luogu.com.cn/problem/P5495
给一个数列 a a a,长度在 2 × 1 0 7 2\times10^{7} 2×107以内,让你求一个数列 b b b满足
b k = ∑ i ∣ k a i b_k=\sum_{i|k}{a_i} bk?=i∣k∑?ai?
- 因为是根据 a a a推 b b b,我们可以直接枚举 i i i,设它的倍数为 j j j,那么有 b [ j ] = b [ j ] + a [ i ] b[j]=b[j]+a[i] b[j]=b[j]+a[i]这样得到的 b b b就是要求的数列,这样的时间复杂度是 O ( n l n n ) O(nlnn) O(nlnn)的,因为调和级数趋近于 l n n + C lnn+C lnn+C
- 由于范围过大, O ( n l o g n ) O(nlogn) O(nlogn)的时间复杂度会被卡,需要再压缩时间,这其实和欧拉筛是一个道理,我们发现,如果采用这种方法比如说 b 8 = a 1 + a 2 + a 4 + a 8 b_8=a_1+a_2+a_4+a_8 b8?=a1?+a2?+a4?+a8?,需要全加上,这里面有很多重复计算,因为 b 4 = a 1 + a 2 + a 4 b_4=a_1+a_2+a_4 b4?=a1?+a2?+a4?,所以其实 b 8 = b 4 + a 8 b_8=b_4+a_8 b8?=b4?+a8?,其他一个道理,所以我们使用质数作为因子进行加速,可以把时间复杂度降到 O ( n l o g l o g n ) O(nloglogn) O(nloglogn)
上面模板题
#include <bits/stdc++.h>using namespace std;typedef long long ll;
const int MAXN = 2e7;
int p[MAXN + 100];
bool vis[MAXN + 100];
int Prime(){
int tot = 0;for(int i=2;i<=MAXN;i++){
if(!vis[i]) p[tot++] = i;for(int j=0;j<tot&&p[j]*i<=MAXN;j++){
vis[i * p[j]] = 1;if(i % p[j] == 0) break;}}return tot;
}
#define uint unsigned int
uint seed;
inline uint getnext(){
seed^=seed<<13;seed^=seed>>17;seed^=seed<<5;return seed;
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);int tt = Prime();int n;cin >> n >> seed;vector<uint> a(n + 1);for(int i=1;i<=n;i++) a[i] = getnext();for(int i=0;i<tt;i++){
for(int j=1;p[i]*j<=n;j++){
a[p[i]*j] += a[j];}}uint ans = 0;for(int i=1;i<=n;i++){
ans ^= a[i];}cout << ans;return 0;
}
Dirichlet后缀和
概述
给定 { a n } \{a_n\} { an?},求 { b n } \{b_n\} { bn?},使得 b k = ∑ k ∣ d a d b_k=\sum_{k|d}a_d bk?=k∣d∑?ad?
代码
for(int i=1;i<=CntPrime;i++)for(int j=N/Prime[i];j>=1;j--)B[j]+=B[ j*Prime[i] ];
例题
https://codeforces.com/contest/1614/problem/D1
https://codeforces.com/contest/1614/problem/D2
题意就是给出 { a n } \{a_n\} {
an?},让你构造一个数组 { b n } \{b_n\} {
bn?},使得其满足前缀 G C D GCD GCD的和最大,求这个最大的和
- easy版本数据范围是2e6,hard版本数据范围是2e7,就是用不用素数优化的关系
- 用数组 c n t [ i ] cnt[i] cnt[i]表示原数组中是 i i i的倍数的数的个数,那么求这个数组显然应该是狄利克雷后缀和,可以仔细想一下,然后打印结果看看
- 考虑dp,设 d p [ i ] dp[i] dp[i]表示最后一个元素被 i i i整除的最大值,设 j j j表示 i i i的倍数,由 i i i推 j j j,有 d p [ j ] = m a x ( d p [ j ] , d p [ i ] + c n t [ j ] × ( j ? i ) ) dp[j]=max(dp[j],dp[i]+cnt[j]\times(j-i)) dp[j]=max(dp[j],dp[i]+cnt[j]×(j?i))其中 j ? i j-i j?i表示 j j j相对于 i i i的增量
#include <bits/stdc++.h>using namespace std;const int MAXN = 5e6;
long long cnt[MAXN + 100];
long long dp[MAXN + 100];
int main(){
ios::sync_with_stdio(false);cin.tie(0);int n;cin >> n;for(int i=0;i<n;i++){
int x;cin >> x;cnt[x] += 1;}for(int i=1;i<=MAXN;i++){
for(int j=i+i;j<=MAXN;j+=i){
cnt[i] += cnt[j];}}dp[1] = cnt[1];long long ans = 0;for(int i=1;i<=MAXN;i++){
for(int j=i+i;j<=MAXN;j+=i){
dp[j] = max(dp[j], 1ll * cnt[j] * (j - i) + dp[i]);}ans = max(ans, dp[i]);// cout << dp[i] << '\n';}cout << ans;return 0;
}
用质数优化
#include <bits/stdc++.h>using namespace std;typedef long long ll;
const int MAXN = 2e7;
int p[MAXN + 100];
bool vis[MAXN + 100];
int cnt[MAXN + 100];
ll dp[MAXN + 100];
int Prime(){
int tot = 1;for(int i=2;i<=MAXN;i++){
if(!vis[i]) p[tot++] = i;for(int j=1;j<tot&&p[j]*i<=MAXN;j++){
vis[i * p[j]] = 1;if(i % p[j] == 0) break;}}return tot;
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);int n;int tt = Prime();cin >> n;for(int i=0;i<n;i++){
int x;cin >> x;cnt[x] += 1;}for(int i=1;i<tt;i++){
for(int j=MAXN/p[i];j>=1;j--){
cnt[j] += cnt[j * p[i]];}}dp[1] = cnt[1];long long ans = 0;for(int i=1;i<=MAXN;i++){
for(int j=1;i*p[j]<=MAXN;j++){
int k = p[j] * i;dp[k] = max(dp[k], 1ll * cnt[k] * (k - i) + dp[i]);}ans = max(ans, dp[i]);// cout << dp[i] << '\n';}cout << ans;return 0;
}
未完待续
- 这似乎和莫比乌斯反演有点关系,留个坑