Dirichlet 前缀及后缀和 Divan and Kostomuksha

这篇博客详细介绍了狄利克雷前缀和与后缀和的概念,以及它们在数列求解中的应用。通过代码示例解释了如何利用质数优化算法,将时间复杂度降低到O(nloglogn)。同时,给出了相关编程竞赛题目链接,展示了这两种方法在解决实际问题中的有效性。

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=dkad

代码

例题处分析

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=ikai

  • 因为是根据 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=kdad

代码

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]×(ji))其中 j − i j-i ji表示 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;
}

未完待续

  • 这似乎和莫比乌斯反演有点关系,留个坑
### Dirichlet分布与数据异构性的关系 在分布式机器学习环境中,特别是在联邦学习场景下,数据通常不是独立同分布(non-IID),这带来了显著的学习挑战[^1]。为了更真实地模拟这种非IID特性,研究者们引入了Dirichlet分布在数据划分中的应用。 #### 使用Dirichlet分布建模数据异构性 Dirichlet分布是一种多维概率分布函数,常用于表示多项式分布参数的概率分布情况。当应用于数据集分割时,可以通过调整Dirichlet分布的超参数α来控制不同客户端之间类别标签的比例差异程度。较小的α值会使得某些类别的样本集中在少数几个节点上;而较大的α则倾向于均匀分配各个类别的样本到不同的节点中[^3]。 具体来说,在构建实验环境或仿真过程中,可以利用Python实现如下代码片段来进行基于Dirichlet分布的数据分发: ```python import numpy as np from sklearn.datasets import make_classification from scipy.stats import dirichlet def distribute_data(X, y, num_clients=10, alpha=0.5): n_classes = len(np.unique(y)) # Generate a distribution over classes for each client using the Dirichlet distribution. proportions = dirichlet(alpha * np.ones(n_classes)).rvs(num_clients) # Normalize row-wise and generate class indices per client based on these probabilities. cumulative_proportions = (proportions.T / proportions.sum(axis=1)).T.cumsum(axis=1) idx_slice_per_client = [[] for _ in range(num_clients)] shuffled_indices = np.random.permutation(len(y)) for i, label in enumerate(y[shuffled_indices]): j = next(j for j, p in enumerate(cumulative_proportions[:,label]) if i/len(y) < p) idx_slice_per_client[j].append(shuffled_indices[i]) X_split = [X[idx] for idx in idx_slice_per_client] y_split = [[y[idx]] for idx in idx_slice_per_client] return X_split, y_split ``` 此段代码展示了如何根据指定数量的客户端以及给定的alpha参数创建一个遵循Dirichlet分布模式下的数据子集列表。通过这种方式生成的数据集能够更好地反映实际应用场景中存在的数据不平衡现象,从而有助于评估算法在这种复杂条件下的性能表现。 ### 应用实例:缓解异构数据带来的挑战 针对由上述方式产生的高度异构化的本地数据集所引起的模型偏差问题,一种有效的解决方案是在全局更新阶段仅同步共享特征提取部分权重,并允许每个参与者单独优化其自身的分类器层。这种方法不仅减少了因局部数据倾斜而导致的整体性能下降风险,同时也提高了最终融合版本对于各类别预测能力的一致性稳定性[^2]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Clarence Liu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值