【Learning】积性函数前缀和——洲阁筛(min_25写法)

本文详细介绍了洲阁筛算法的工作原理及其应用。该算法适用于求解较大范围内积性函数的前缀和问题,特别是当函数在质数处的取值较为简单时。文章通过实例解释了如何使用两个辅助数组g和h进行高效计算,并提供了具体实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题描述

  
  洲阁筛解决的问题主要是\(n\)范围较大的积性函数前缀和。
  
​  已知一积性函数\(f(i)\),求\(\sum_{i=1}^nf(i)\)
  
  \(n\leq10^{12}\).
  
  
  

求解方法

  
  如果\(f(i)\)在质数处的取值比较简单,那么可以运用洲阁筛来求解。
  
​  我们需要两个辅助数组。
  

\(g_{i,j}\)

  
  定义如下:
\[ \begin{aligned} g_{i,j}&=\sum_{k=2}^i[k与p_1,p_2,...,p_j互质或就是其中某个质数]\; s(k)\\ &=\sum_{k=2}^i[k是\leq p_j的质数或k的最小质因子大于p_j]\; s(k) \end{aligned} \]
  其中\(s(x)\)是一个完全积性函数,它可以是\(s(x)=x\),或\(s(x)=1\),等等。
  
​  我们一般要求第二维计算到\(m\),其中\(m\)为满足\(p_m\le\sqrt n\)的最大正整数。
  
  这时候,\(g_{x,m}\)就表示函数\(s\)在前缀范围\([1,x]\)内的质数处的取值之和,这也是\(g\)数组的主要作用。但这里\(x\)的定义域不是\([1,n]\),下文会提到。
   
  这个数组怎么求呢?
  
​  首先边界条件比较简单:
\[ g_{i,0}=\sum_{k=2}^is(k) \]
  可是这里有一个限制:\(\sum_{k=2}^is(k)\)必须足够简单好算。如果函数前缀和比较复杂,如\(s=\mu\),就不能使用在\(s=\mu\)意义下计算\(g\)的这种方法。但是如果函数在质数的取值比较简单,如\(\mu(p)=-1\),我们可以考虑换一个角度:令\(s(x)=1\),可以计算出\([1,n]\)的质数个数\(p_{sum}\),那么我们可以用\(-1*p_{sum}\)表示所求的东西,一样达到了我们的目的(黑体字)。
  
  考虑由\(g_{i,j-1}\)推出\(g_{i,j}\)
  
  若\(i<p_j^2\),显然\(g_{i,j}=g_{i,j-1}\)
  
  否则当\(i\ge p_j^2\)时,如何考虑?\(g_{i,j-1}\)代表着一些在\(j-1\)时合法的数的函数值之和,而从\(j-1\)变成\(j\)时,若某些原本合法的数变得不合法,显然一定是因为触犯了第二个条件:最小质因子恰好为\(p_j\)。如何算出这一部分的数的函数值之和呢?
\[ g_{i,j}=g_{i,j-1}-s(p_j)(g_{\lfloor\frac i {p_j}\rfloor,j-1}-g_{p_j-1,j-1}) \]
  
  
​  后面减去的部分就是这一部分数的函数值之和。首先它们都有一个最小质因子\(p_j\),随后,它们除去\(p_j\)剩下的部分,不可以含有小于\(p_j\)的质因子,因此剩下的部分至少大于等于\(p_j\),但要小于等于\(\frac{i}{p_j}\)。这恰好对应了\(g\)的定义!后面一部分算的就是
\[ \sum_{k=p_j}^{\frac i {p_j}}[最小质因子>p_{j-1}]\; s(k) \]
  但是\(n\)太大,存不下怎么办?
  
  记集合\(S=\{\lfloor\frac n x\rfloor |x\in[1,n]\}\;\;(\)\(|S|=2\sqrt n)\),我们只需要关注\(g_{i,...}\;\;(i\in S)\)即可,那为什么只用考虑第一维取这些值的情况呢?可以证明,所有以后需要调用的第一维都属于\(S\)。从\(g\)本身的递推需要来看,我们需要\(g_{\lfloor\frac i {p_j}\rfloor,j-1}\),它的第一维\(\lfloor \frac i {p_j}\rfloor\in S\);还需要\(g_{p_j-1,j-1}\),它的第一维\(p_j-1\le\sqrt n\),一定属于\(S\)。所以,其他的第一维取值就不需要计算了。从下文的\(h\)计算的调用来看,也都只会调用到\(S\)内的第一维,下文会提到。
  
  再之,我们发现每次递归时只用到第二维为\(j-1\)的数据,这给了我们滚动的思想:我们依次计算\(j=0...m\)的情况。将\(|S|\)个元素排成一排,分别表示在当前\(j\)意义下,\(g_{i,j},i\in S\)的取值。由于更新\(g_{i,j}\)的时候只需要用到诸如\(k\le i\)\(g_{k,j-1}\),我们从大到小枚举并更新那些需要更新的元素\(i\in S,i\ge p_j^2\),这样可以保证调用的元素仍然是\(j-1\)意义下的;而\(i<p_j^2\)的情况,与\(j-1\)时完全没有改变,不需要操作,直接继承。
  
​  \(j\)越是大,我们需要更新的元素就越来越少。如此一层层地覆盖上去(很像一维背包的那种继承思想),我们,就可以计算到\(g_{n,m}\)了。这一步的复杂度是\(O(\frac {n^{\frac 3 4}}{\log n})\)
  
  离散\(S\)中元素到数组上的方法很简单,对于\(x\in S\),如果\(x\le \sqrt n\),用\(pos_1[x]\)表示\(x\)的离散位置;如果\(x>\sqrt n\),用\(pos_2[\lfloor \frac n x\rfloor]\)表示\(x\)的离散位置。可以写一个简短的\(get()\)函数来处理询问离散位置的操作。
  

\(h_{i,j}\)

  
  定义如下:
\[ h_{i,j}=\sum_{k=2}^i[k的最小质因子\ge p_j]\;f(k) \]
  其递推式为:
\[ \begin{aligned} h_{i,j}&=\sum_{p_k\ge p_j}\sum_{e\ge 1且p_k^e\le i}f(p_k^e)h_{\lfloor \frac i{p_k^e}\rfloor,j+1}+f(p_k^e)\\ &=\sum_{p_k\ge p_j}\sum_{e\ge 1且p_k^e\le i}f(p_k^e)(h_{\lfloor \frac i{p_k^e}\rfloor,j+1}+1) \end{aligned} \]
  它相当于枚举所有在\([2,i]\)范围内,最小质因子大于等于\(p_j\)的数,并对函数值求和。首先枚举它们的最小质因数\(p_k\),再枚举最小质因数\(p_k\)的幂;最后统计有多少个数,满足除去\(p_k^e\)后剩余部分最小质因子大于\(p_j\),也就是大于等于\(p_{j+1}\),这和\(h\)本身的定义恰好符合。如此枚举,不会算重,但是会漏掉一种情况:\(f(p_k^e)\)没有被算入,因为\(h\)\(\sum\)是从2开始枚举的。额外加上即可。
  
  我们使用搜索计算\(h\)。这里的搜索不需要记忆化,因为有结论是:如果要求不同的\(h_{i,j}\),它们在搜索时不会搜索到重复的地方。
  
  第一个循环不可以完全枚举\(k=j...m\),不然的话复杂度过高。当\(p_k^2>i\)时,余下的未计算的函数值之和恰好是函数在\(p\in(\sqrt i,i]\)处的取值之和,可以直接用\(g_{i,m}-g_{p_{m'},m}\)表示,其中\(m'\)为满足\(p_{m'}\le \sqrt i\)的最大正整数。我们发现\(p_m',i\in S\),回应上文,只需要计算\(g\)的第一维属于\(S\)时的情况。
  
  
  

答案计算

  
  \(\sum_{i=2}^nf(i)=h_{n,0}\)
    
  那么\(\sum_{i=1}^nf(i)=h_{n,0}+f(1)\),简洁明了。
  
  当然,有时候题目甚至不需要调用\(h\)数组,这要依题目而灵活变化。

  
  
  

总结

  
  整体思路稍微有点复杂,但只要明白了洲阁筛对积性函数求和的关键步骤,就可以比较好地理解。
  
  首先,我们所讨论的积性函数,最好在质数处有简明的表达式。我们可以将表达式写出后,对于每个和式,用\(g\)在不同\(s(x)\)的意义下逐个求解。
  
  然后要理解洲阁筛对分配律的利用。它通过枚举最小质因数、枚举其作为最小质因数的指数、最后统计除去最小质因数后,剩余部分最小质因数大于自己的情况数这一种枚举方法,可以结合积性函数性质、运用\(h\)数组快速枚举遗漏的数,并得到其函数值之和。
  
  题目所求的问题并不会总是中规中矩,我们需要把题目求的表达式拆成一个一个部分,尽量用g或h的定义来表示,依次求解。
   
  总的时间复杂度为\(O(\frac{n^{\frac 3 4}}{\log n})\)
  
  
  

代码

  
  以SPOJ的DivcntK为例,求的是\(\sum_{i=1}^n \sigma_0(i^k)\)\(n,k\le 10^{10}\)。这份代码是单组数据的。
  

#include <cstdio>
#include <cmath>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int SQRTN=100005;
bool vis[SQRTN];
ll p[SQRTN],pcnt;
ll n,k,sqrtn;
int m;
ll a[SQRTN*2],cnt;
int pos1[SQRTN],pos2[SQRTN];
ull g[SQRTN*2];
void prework(){
    for(int i=2;i<SQRTN;i++){
        if(!vis[i])
            p[++pcnt]=i;
        for(int j=1;j<=pcnt&&i*p[j]<SQRTN;j++){
            int x=i*p[j];
            vis[x]=true;
            if(i%p[j]==0) break;
        }
    }
}
int gp(ll x){//getpos
    return x<=sqrtn?pos1[x]:pos2[n/x];
}
void Discretization(){
    for(ll i=1,j;i<=n;i=j+1){
        a[++cnt]=n/i;
        j=n/(n/i);
    }
    reverse(a+1,a+1+cnt);
    for(int i=1;i<=cnt;i++)
        if(a[i]<=sqrtn) pos1[a[i]]=i;
        else pos2[n/a[i]]=i;
}
void calc_g(){
    for(int i=1;i<=cnt;i++) g[i]=a[i]-1;
    for(int j=1;j<=m;j++)
        for(int i=cnt;i>=1&&a[i]>=p[j]*p[j];i--)
            g[i]-=g[gp(a[i]/p[j])]-g[gp(p[j]-1)];
}
ull calc_h(ll i,ll j){
    if(i<=1) return 0;
    ull res=0;
    int a;
    for(a=j;a<=m&&p[a]*p[a]<=i;a++)
        for(ll pe=p[a],e=1;pe<=i;pe*=p[a],e++)
            res+=(ull)(e*k+1)*(calc_h(i/pe,a+1)+1);
    if(p[a-1]<=i) 
        res+=(ull)(k+1)*(g[gp(i)]-g[gp(p[a-1])]);
    return res;
}
int main(){
    prework();
    scanf("%lld%lld",&n,&k);
    sqrtn=(ll)sqrt(n);  
    m=upper_bound(p+1,p+1+pcnt,sqrtn)-p-1;
    Discretization();
    calc_g();
    ull ans=(ull)(k+1)*(g[gp(n)]-m);
    for(int i=1;i<=m;i++)
        for(ll pe=p[i],e=1;pe<=n;pe*=p[i],e++)
            ans+=(ull)(e*k+1)*(calc_h(n/pe,i+1)+1);
    ans++;
    cout<<ans<<endl;
    return 0;
}

转载于:https://www.cnblogs.com/RogerDTZ/p/9184689.html

1.未使用的 import 语句 'from sklearn.model_selection import TimeSeriesSplit' 2.在 '__init__.py | __init__.py' 中找不到引用 'keras' 针对这两个问题修改下段代码:import pandas as pd import numpy as np import tensorflow as tf from sklearn.preprocessing import MinMaxScaler from sklearn.model_selection import TimeSeriesSplit from sklearn.metrics import mean_absolute_error, mean_squared_error from pyswarms import single as ps_single import matplotlib.pyplot as plt import seaborn as sns # 环境检查 print(f"TensorFlow版本: {tf.__version__}") print(f"GPU可用: {'是' if tf.config.list_physical_devices('GPU') else '否'}") # 加载风电数据集 wind_data = pd.read_csv('D:\数据表\小波变化去噪.csv', parse_dates=['timestamp']) print(f"数据集大小: {wind_data.shape}") # 特征工程处理 def preprocess_data(df): # 时间特征提取 df['hour'] = df['timestamp'].dt.hour df['day_of_year'] = df['timestamp'].dt.dayofyear # 修正命名规范 # 风向周期处理($\theta \rightarrow (\sin\theta, \cos\theta)$) df['wind_dir_sin'] = np.sin(df['wind_direction'] * (2 * np.pi / 360)) df['wind_dir_cos'] = np.cos(df['wind_direction'] * (2 * np.pi / 360)) # 选择特征 feature_cols = ['wind_speed', 'temperature', 'pressure', 'hour', 'day_of_year', 'wind_dir_sin', 'wind_dir_cos', 'power'] # 归一化 data_scaler = MinMaxScaler(feature_range=(0, 1)) scaled_array = data_scaler.fit_transform(df[feature_cols]) return scaled_array, data_scaler # 创建时间序列样本 def create_timeseries_dataset(data, look_back=24, forecast_horizon=1): X, Y = [], [] for i in range(len(data) - look_back - forecast_horizon): X.append(data[i:i + look_back, :-1]) # 输入特征 Y.append(data[i + look_back:i + look_back + forecast_horizon, -1]) # 输出功率 return np.array(X), np.array(Y) # 执行预处理 scaled_wind_data, data_scaler = preprocess_data(wind_data) X_data, y_data = create_timeseries_dataset(scaled_wind_data, look_back=24, forecast_horizon=1) # 数据集拆分 train_size = int(len(X_data) * 0.8) X_train, X_test = X_data[:train_size], X_data[train_size:] y_train, y_test = y_data[:train_size], y_data[train_size:] print(f"训练集: {X_train.shape}, 测试集: {X_test.shape}") # GRU模型架构 def build_gru_model(input_shape, gru_units=64, dropout_rate=0.2, learning_rate=0.001): model = tf.keras.Sequential([ tf.keras.layers.GRU(gru_units, input_shape=input_shape, return_sequences=True), tf.keras.layers.Dropout(dropout_rate), tf.keras.layers.GRU(int(gru_units * 0.8)), tf.keras.layers.Dropout(dropout_rate), tf.keras.layers.Dense(1) # 输出层预测功率 ]) model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=learning_rate), loss='mean_squared_error', metrics=['mae'] ) return model # PSO优化函数 def optimize_with_pso(X_train_data, y_train_data): def objective_function(params): loss_values = [] n_particles = params.shape[0] for i in range(n_particles): gru_units = int(params[i, 0]) lr = params[i, 1] epochs = int(params[i, 2]) batch_size = int(params[i, 3]) # 创建并训练模型 model = build_gru_model( (X_train_data.shape[1], X_train_data.shape[2]), gru_units=gru_units, learning_rate=lr ) # 使用早停机制防止过拟合 early_stop = tf.keras.callbacks.EarlyStopping( monitor='val_loss', patience=3, restore_best_weights=True ) history = model.fit( X_train_data, y_train_data, epochs=epochs, batch_size=batch_size, validation_split=0.2, verbose=0, callbacks=[early_stop] ) # 使用最小验证损失作为评估标准 val_loss = min(history.history['val_loss']) loss_values.append(val_loss) return np.array(loss_values) # 参数边界(GRU单元数, 学习率, 迭代次数, 批次大小) bounds = ([32, 0.0001, 10, 16], [256, 0.01, 100, 128]) # 设置PSO选项 options = {'c1': 0.5, 'c2': 0.3, 'w': 0.9} # 初始化优化器 optimizer = ps_single.GlobalBestPSO( n_particles=10, dimensions=4, options=options, bounds=bounds ) # 执行优化 best_params, best_loss = optimizer.optimize(objective_function, iters=10) return { 'gru_units': int(best_params[0]), 'learning_rate': best_params[1], 'epochs': int(best_params[2]), 'batch_size': int(best_params[3]) } # 执行PSO优化 optimal_params = optimize_with_pso(X_train, y_train) print("PSO优化结果:", optimal_params) # 使用优化参数训练最终模型 final_model = build_gru_model( (X_train.shape[1], X_train.shape[2]), gru_units=optimal_params['gru_units'], learning_rate=optimal_params['learning_rate'] ) history = final_model.fit( X_train, y_train, epochs=optimal_params['epochs'], batch_size=optimal_params['batch_size'], validation_split=0.2, verbose=1 ) # 模型评估 test_loss, test_mae = final_model.evaluate(X_test, y_test) print(f"测试集损失: {test_loss:.4f}, 测试集MAE: {test_mae:.4f}") # 预测 y_pred = final_model.predict(X_test) # 反归一化($y_{\text{actual}} = y_{\text{scaled}} \times \sigma + \mu$) y_test_actual = y_test * data_scaler.scale_[-1] + data_scaler.min_[-1] y_pred_actual = y_pred.flatten() * data_scaler.scale_[-1] + data_scaler.min_[-1] # 计算评估指标 mae = mean_absolute_error(y_test_actual, y_pred_actual) rmse = np.sqrt(mean_squared_error(y_test_actual, y_pred_actual)) print(f"MAE: {mae:.2f} MW, RMSE: {rmse:.2f} MW") # 可视化结果 plt.figure(figsize=(15, 10)) # 损失曲线 plt.subplot(2, 2, 1) plt.plot(history.history['loss'], label='训练损失') plt.plot(history.history['val_loss'], label='验证损失') plt.title('模型训练损失曲线') plt.ylabel('MSE损失') plt.xlabel('轮次') plt.legend() # 预测对比 plt.subplot(2, 2, 2) plt.plot(y_test_actual[:100], label='实际功率') plt.plot(y_pred_actual[:100], label='预测功率') plt.title('风电功率预测结果对比') plt.ylabel('功率(MW)') plt.xlabel('时间步') plt.legend() # 残差分布($\epsilon = y_{\text{actual}} - \hat{y}$) plt.subplot(2, 2, 3) residuals = y_test_actual - y_pred_actual sns.histplot(residuals, kde=True, bins=30) plt.title('预测残差分布') plt.xlabel('预测误差(MW)') # 散点图($\hat{y} \text{ vs } y_{\text{actual}}$) plt.subplot(2, 2, 4) plt.scatter(y_test_actual, y_pred_actual, alpha=0.3) plt.plot([y_test_actual.min(), y_test_actual.max()], [y_test_actual.min(), y_test_actual.max()], 'r--') plt.title('预测值 vs 实际值') plt.xlabel('实际功率(MW)') plt.ylabel('预测功率(MW)') plt.tight_layout() plt.savefig('wind_power_prediction_results.png') plt.show()
最新发布
06-06
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值