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

测试地址:Clarke and math
题目大意:fg是定义在集合{1,2,...,n}的一个函数,且g(i)=i1|ii2|i1...ik|ik1f(ik),给定kf(1),f(2),...f(n),求g(1),g(2),...,g(n)
做法:此题需要用到狄利克雷卷积+快速幂。
定义两个算术函数fg的狄利克雷卷积为:(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...IfkI),由于狄利克雷卷积满足结合律,所以可以用快速幂加速计算。在计算狄利克雷卷积的时候,如果对于每一个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;
}
posted @ 2017-06-17 21:08  Maxwei_wzj  阅读(278)  评论(0编辑  收藏  举报