【HDU5628】Clarke and math-狄利克雷卷积+快速幂

测试地址:Clarke and math
题目大意: f g是定义在集合 {1,2,...,n} 的一个函数,且 g(i)=i1|ii2|i1...ik|ik1f(ik) ,给定 k f(1),f(2),...f(n),求 g(1),g(2),...,g(n)
做法:此题需要用到狄利克雷卷积+快速幂。
定义两个算术函数 f g的狄利克雷卷积为: (fg)(n)=d|nf(d)g(n/d) ,那么函数的狄利克雷卷积满足以下性质:
交换律: fg=gf
结合律: f(gh)=(fg)h
分配律: f(g+h)=fg+fh
那么再看我们要求的函数 g ,我们一层一层展开,设函数I(n)=1,则:
f(ik1)=ik|ik1f(ik)=ik|ik1f(ik)I(ik1/ik) ,即 f=If
f′′(ik2)=ik1|ik2f(ik1)=ik1|ik2f(ik1)I(ik2/ik1) ,即 f′′=If=IIf
...
这样一直推导下去,得到 g=III...If k I),由于狄利克雷卷积满足结合律,所以可以用快速幂加速计算。在计算狄利克雷卷积的时候,如果对于每一个 1in 都按照定义枚举约数计算贡献的话,时间复杂度显然爆炸,所以我们可以枚举约数,然后枚举有哪些 i 需要加上这个约数的贡献即可,那么计算一次狄利克雷卷积的复杂度为O(nlogn),总的时间复杂度就是 O(nlognlogk)
注意如果用结构体传参会爆栈,直接用数组+指针传参就可以了。以及这题的格式要求行末不能有空格,但要有换行,注意一下就行了。
狄利克雷卷积的作用当然不只是做这种题,它为一堆有关莫比乌斯反演和杜教筛的式子提供了简明的记号,这个后面有时间再进行小结,这里就不再赘述了。
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mod 1000000007
#define ll long long
using namespace std;
int T,n,k;
ll f[100010],g[100010],s[100010],ss[100010],pro[100010];

void mult(ll *a,ll *b)
{
  memset(s,0,sizeof(s));
  for(int i=1;i*i<=n;i++)
  {
    s[i*i]=(s[i*i]+a[i]*b[i])%mod;
    for(int j=i+1;i*j<=n;j++)
      s[i*j]=(s[i*j]+a[i]*b[j]+a[j]*b[i])%mod;
  }
  for(int i=1;i<=n;i++) a[i]=s[i];
}

void power(ll *a,int p)
{
  for(int i=1;i<=n;i++) ss[i]=a[i];
  memset(pro,0,sizeof(pro));
  pro[1]=1;
  while(p)
  {
    if (p&1) mult(pro,ss);
    mult(ss,ss);p>>=1;
  }
  for(int i=1;i<=n;i++) a[i]=pro[i];
}

int main()
{
  scanf("%d",&T);
  while(T--)
  {
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
    {
      scanf("%lld",&f[i]);
      g[i]=1;
    }
    power(g,k);
    mult(f,g);
    for(int i=1;i<=n;i++)
      printf("%lld%c",f[i],(i==n)?'\n':' ');
  }

  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值