【训练题65:数论 | 推式子优化】Convolution | 2021牛客暑期多校训练营4

题意

  • Convolution | 2021牛客暑期多校训练营4
    假设 x , y x,y x,y 的质因子分解为 x = ∏ p i s i , y = ∏ p i t i x=\prod p_i^{s_i},y=\prod p_i^{t_i} x=pisi,y=piti
    我们定义运算符 x ⊗ y = ∏ p i ∣ s i − t i ∣ x\otimes y=\prod p_i^{|s_i-t_i|} xy=pisiti
    现在给你一个长度为 n n n 的序列 a n a_n an 还有一个 c c c
    现在已知:
    b i = ∑ i = j ⊗ k a i ⋅ k c b_i=\sum_{i=j\otimes k} a_i\cdot k^c bi=i=jkaikc
    其中 1 ≤ i , j , k ≤ n 1\le i,j,k\le n 1i,j,kn ,后文省略
    你需要求出每一个 b i b_i bi (取模 998244353 998244353 998244353) ,输出他们的异或和即可。
  • 1 ≤ n ≤ 1 0 6 1\le n\le 10^6 1n106
    0 ≤ a i < 998244353 0\le a_i<998244353 0ai<998244353
    0 ≤ c ≤ 1 0 9 0\le c\le 10^9 0c109

题意

  • 过程非常难想的推式子题目…
    首先,我们直接枚举 i , j , k i,j,k i,j,k 当然会超时,考虑 ⊗ \otimes 运算符的本质
    其实就是
    x ⊗ y = l c m ( x , y ) gcd ⁡ ( x , y ) = x ⋅ y gcd ⁡ ( x , y ) 2 x\otimes y=\frac{lcm(x,y)}{\gcd(x,y)}=\frac{x\cdot y}{\gcd(x,y)^2} xy=gcd(x,y)lcm(x,y)=gcd(x,y)2xy
    l c m lcm lcm 式子肯定不好搞,我们使用右边的式子。
    所以现在就变成了:
    b i = ∑ j = 1 n ∑ k = 1 n [ j ⋅ k gcd ⁡ ( j , k ) 2 = i ] × a j × k c b_i=\sum_{j=1}^n \sum_{k=1}^n \Big[ \frac{j\cdot k}{\gcd(j,k)^2}=i \Big] \times a_j \times k^c bi=j=1nk=1n[gcd(j,k)2jk=i]×aj×kc
    很套路的,我们枚举 gcd ⁡ ( j , k ) = d \gcd(j,k)=d gcd(j,k)=d ,同时对称性构造 j ′ = j d , k ′ = k d j^\prime =\frac{j}{d},k^\prime =\frac{k}{d} j=djk=dk ,其中要求 j ′ ⊥ k ′ j^\prime \perp k^\prime jk
    这个时候式子就变成了:
    b i = ∑ j = 1 n ∑ k = 1 n [ j ′ ⋅ k ′ = i ] × a j × k c b_i=\sum_{j=1}^n \sum_{k=1}^n \Big[j^\prime \cdot k^\prime =i \Big] \times a_j \times k^c bi=j=1nk=1n[jk=i]×aj×kc
    这么一看,就是枚举 i i i 的两个因子嘛!
    我们转换枚举方式,先枚举一个因子 j ′ j^\prime j ,再枚举其倍数 d ⋅ j ′ d\cdot j^\prime dj ,变成了:
    b i = ∑ j ′ ∣ i ∑ d = 1 min ⁡ { n j ′ , n k ′ } a d j ′ × ( d k ′ ) c b_i=\sum_{j^\prime | i} \sum_{d=1}^{\min\{\frac{n}{j^\prime},\frac{n}{k^\prime} \}} a_{dj^\prime} \times (dk^\prime)^c bi=jid=1min{jn,kn}adj×(dk)c
    注意为什么 d d d 的上限中,分子是 n n n 不是 i i i 呢?因为 d = gcd ⁡ ( j , k ) d=\gcd(j,k) d=gcd(j,k),明显 1 ≤ j , k ≤ n 1\le j,k\le n 1j,kn
    注意式子中不用枚举 k ′ k^\prime k ,因为此时 i = j ′ k ′ i=j^\prime k^\prime i=jk ,我们对于 i i i 只用枚举 j ′ j^\prime j 就能获得 k ′ k^\prime k
    换句话说, k ′ k^\prime k 是与 d d d 无关的,我们令 d d d 的枚举上限 u p up up ,所以我们自然左移:
    b i = ∑ j ′ ∣ i ( k ′ ) c ∑ d = 1 u p a d j ′ × d c b_i=\sum_{j^\prime | i}(k^\prime)^c \sum_{d=1}^{up} a_{dj^\prime} \times d^c bi=ji(k)cd=1upadj×dc
    这样,复杂度我们变成了 O ( n 2 n ) O(n^2\sqrt n) O(n2n ) (假设我们提前预处理出快速幂 d c d^c dc 的话)
  • 注意到,对于同一个 i i i ,我们第二个求和的式子中,我们重算了许多东西(因为我们只会变 u p up up 的值)
    所以我们记一个辅助数组
    d p [ x ] [ y ] = ∑ d = 1 y a d x × d c dp[x][y]=\sum_{d=1}^y a_{dx}\times d^c dp[x][y]=d=1yadx×dc
    然后原来式子就变成了:
    b i = ∑ j ′ ∣ i ( k ′ ) c × d p [ j ′ ] [ u p ] b_i=\sum_{j^\prime | i}(k^\prime)^c \times dp[j^\prime][up] bi=ji(k)c×dp[j][up]
    注意到,我们从 d p [ j ′ ] [ x ] dp[j^\prime][x] dp[j][x] 推到 d p [ j ′ ] [ x + 1 ] dp[j^\prime][x+1] dp[j][x+1] 是非常好做的,值只增加了 a j ′ ( x + 1 ) ⋅ ( x + 1 ) c a_{j^\prime(x+1)}\cdot (x+1)^c aj(x+1)(x+1)c
    我们还注意到,对于一个 j ′ j^\prime j ,我们不会用到其他的 d p [ x ≠ j ′ ] [ y ] dp[x\ne j^\prime][y] dp[x=j][y] ,所以我们可以省略成一维数组 d p [ u p ] dp[up] dp[up]
    由于 u p up up 不超过 n j ′ \frac{n}{j^\prime} jn ,所以我们获取所有 d p [ u p ] dp[up] dp[up] 的复杂度是 O ( n log ⁡ n ) O(n\log n) O(nlogn)
    所以我们现在,复杂度都在与求 b i b_i bi 数组,复杂度为 O ( n n ) O(n\sqrt n) O(nn )
  • 注意到, b i b_i bi 是求出 i i i 的一个因子 j ′ j^\prime j 然后求和
    我们当然可以先枚举因子 j ′ j^\prime j ,再枚举倍数 k ′ k^\prime k ,此时因子的倍数就是 j ′ ⋅ k ′ = i j^\prime\cdot k^\prime=i jk=i,就是我们的每一个 i i i
    这样复杂度就可以优化成 O ( n log ⁡ n ) O(n\log n) O(nlogn) 了!

代码

  • 时间复杂度: O ( n log ⁡ n ) O(n\log n) O(nlogn)
    为什么分析这么复杂,代码还是这么短啊!
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;
typedef long long ll;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}

const int MAX = 1e6+50;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const double EPS = 1e-5;

ll qpow(ll a,ll n){/* */ll res = 1LL;while(n){if(n&1)res=res*a%MOD;a=a*a%MOD;n>>=1;}return res;}
ll qpow(ll a,ll n,ll p){a%=p;ll res = 1LL;while(n){if(n&1)res=res*a%p;a=a*a%p;n>>=1;}return res;}
ll npow(ll a,ll n){/* */ll res = 1LL;while(n){if(n&1)res=res*a;a=a*a;n>>=1;if(res<0||a<0)return 0;}return res;}
ll inv(ll a){/* */return qpow(a,MOD-2);}
ll inv(ll a,ll p){return qpow(a,p-2,p);}

ll aa[MAX];
ll bb[MAX];
ll powc[MAX];
ll dp[MAX];
int main()
{
    int n,c;n = read();c = read();
    for(int i = 1;i <= n;++i)aa[i] = read_ll();
    for(int i = 0;i <= n;++i)powc[i] = qpow(i,c);		// 预处理出 i^c

    for(int j2 = 1;j2 <= n;++j2){
        for(int up = 1;j2 * up <= n;++up){				// 更新 dp[x] 数组
            dp[up] = (dp[up-1] + aa[j2 * up] * powc[up] % MOD) % MOD;
        }
        for(int k2 = 1;j2 * k2 <= n;++k2){
            if(__gcd(j2,k2) != 1)continue;				// 要求如此
            int i = j2 * k2;
            int up = min(n / j2,n / k2);
            bb[i] = (bb[i] + dp[up] * powc[k2] % MOD) % MOD;	// 更新 b[x] 数组
        }
    }

    ll ans = 0;
    for(int i = 1;i <= n;++i)ans ^= bb[i];
    Print(ans);
    Write();
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值