您的位置:首页 > 其它

[杜教筛]小结

2017-03-16 20:49 155 查看
你问我是参考哪里的,我只能回答无可奉告
http://blog.csdn.net/skywalkert/article/details/50500009
这篇博客如果和tangls的博客有雷同之处,并非偶然

通常我们用艾拉筛,nlogn,通常跑很快。

但是随着oi的发展,我们用到了线性筛,On,跑的更快。(但是这个筛)

当你以为On筛能解决所有事情时,毒瘤出题人已经开始使用O(n23)O(n23)的筛法了。

前置技能

狄利克雷卷积

记(f∗g)(n)=∑d|nf[d]∗g[nd](f∗g)(n)=∑d|nf[d]∗g[nd]

狄利克雷卷积满足交换律,结合律,加法的分配律

通常使用到的是μ∗1=eμ∗1=e

值得一提的是

g(n)=∑d|nf(n)g(n)=∑d|nf(n)

g∗μ=f∗1∗μg∗μ=f∗1∗μ

f∗e=g∗μf∗e=g∗μ

f(n)=∑d|ng(d)μ(n/d)f(n)=∑d|ng(d)μ(n/d)

这就是莫比乌斯反演的一个形式了。

狄利克雷卷积的一个常用技巧是对于积性函数与恒等函数的卷积的处理,但是我目前也是刚入门,之后可能会upd。

需要注意的

∑nii2∑ini2=n(n+1)(2n+1)/6

像i^3这种要是不会推的话就插值大力化简也是可以的。

杜教筛

杜教筛是求一个积性函数的前缀和的

因为刚入门,所以先说说怎么筛phi的前缀和,以下只说实现不考虑证明复杂度。当预处理前n23n23时可以达到O(n23)O(n23)的复杂度。这是可以用积分算的,但是由于某一部分常数的原因,我们设的边界值也是有所波动的。

∑d|nϕ(i)=n∑d|nϕ(i)=n

s(n)=∑niϕ(i)s(n)=∑inϕ(i)

=∑nii−∑j|i,j<iϕ(j)=∑ini−∑j|i,j<iϕ(j)

=n(n+1)/2−∑i∑j|i,j<iϕ(j)−∑i∑j|i,j<iϕ(j)

考虑j会对i提供贡献当且仅当i为j的倍数并且i!=j,不妨把i视为j的倍数,则j对ij提供贡献

=n(n+1)/2−∑i=2∑⌊ni⌋jϕ(j)−∑i=2∑j⌊ni⌋ϕ(j)

=n(n+1)/2−∑i=2s(⌊ni⌋)−∑i=2s(⌊ni⌋)

那么就可以分块计算了。

贴上一个实现的代码,就是线性筛+get_s的部分。

#include<iostream>
#include<string>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cmath>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int n;
int m,pyz;

inline int read()
{
char c;
bool pd=0;
while((c=getchar())>'9'||c<'0')
if(c=='-') pd=1;
int res=c-'0';
while((c=getchar())>='0'&&c<='9')
res=(res<<3)+(res<<1)+c-'0';
return pd?-res:res;
}
const int N=1e6+1;
bool is
;
int prime
;
ll phi
,s
;
int inv6;
inline int ksm(int s,int t)
{
int res=1;
while(t)
{
if(t&1) res=(ll)res*s%pyz;
s=(ll)s*s%pyz;
t>>=1;
}
return res;
}
inline int get_f(int n)
{
return (ll)n*(n+1)%pyz*(n<<1|1)%pyz*inv6%pyz;
}
int tot;
inline ll get_s(int x)
{
if(x<N) return phi[x];
int y=n/x;
if(!y) return 0;
if(s[y]) return s[y];
s[y]=((ll)x*x+x)>>1;
int a,b;
for(int i=2;i<=x;++i)
{
a=x/i;
b=x/(x/i);
s[y]-=get_s(a)*(b-i+1);
i=b;
}
return s[y];
}
int main()
{
//  freopen("b.in","r",stdin);
//  freopen(".out","w",stdout);
n=read();
pyz=read();
phi[1]=1;
inv6=ksm(6,pyz-2);
m=sqrt(n);
for(int i=2;i<N;++i)
{
if(!is[i]) prime[++prime[0]]=i,phi[i]=(i-1)%pyz;
for(int j=1;j<=prime[0]&&i*prime[j]<N;++j)
{
int tt=i*prime[j];
is[tt]=1;
if(!(i%prime[j]))
{
phi[tt]=phi[i]*prime[j];
break;
}
phi[tt]=phi[i]*(prime[j]-1);
}
}
int ans=0;
for(int i=1;i<N;++i) phi[i]=phi[i-1]+phi[i];
int a,b;
for(int i=1;i<=n;++i)
{
a=n/i;
b=n/a;
ans=(ans+get_f(a)*(get_s(b)-get_s(i-1)))%pyz;
i=b;
}
printf("%d",ans);
}


然后是唐老师博客的做题记录

筛phi的很好做,不提了

筛mu的很好做,不提了



看到这里的时候我已经有非常不好的预感了

果然下面就gg了



就是这个A,唐老师是先做一番化简,变成积性函数前缀和的样子,我先跟着化简了一波。前面的都好推,就最后那个式子可以说是十分的骚了。因为没有注意到1的贡献少了0.5,推了好几遍。

后来问了下uoj的同学,好像这个东西用phi特别好推,但是我这个只会mu的选手真的很惨啊

我是这样推得



用phi来简单解释



难怪tangls只是简单的用一个等号。。

来自一个不愿透露姓名的选手的回复:

这太好推了吧

若n为1,答案就是1

否则将i与n-i配对

就是如果i与n互质,那么n-i与n也互质

然后你可以理解为江它们配对求和

化成积性函数前缀和之后就是我们熟悉的形式了。

我们的目的始终是求F(n),也就是后来的G(n)。再转到ϕ′ϕ′这些都是自然而然的事情。

因为我们一直求的都是前缀和。杜教筛的一个最基本也是最核心的思想就是通过一个容易求前缀和的函数来容斥分块来求第n项。当前出现的一个最大的困难就是到了ϕ′ϕ′这一步后我们没有了前进的方向,他既不能被化简,也不能方便的求前缀和来求他的第n项。tangls这时候表演了

如果能通过狄利克雷卷积构造一个更好计算前缀和的函数,且用于卷积的另一个函数也易计算,则可以简化计算过程。例如上题就是利用了φ∗I=id的性质,但一定注意,不是所有的这一类题都只用配个恒等函数I就可以轻松完事的,有时需要更细致的观察。

当一个函数容易得到前缀和的时候,就是杜教的工作了。简单的把公式回代后就可以得出解了。

tangls的那个文章下面还有一些练习题。我可能会做其中的几道然后写一些题解。

upd:18_3_22

让你求积性函数前缀和的时候,通常原函数的前缀和是很难求的,我们尝试构造一个函数使得这个函数和积性函数卷起来后的前缀和变得好求。接下来就能用

前缀和=∑ni∑d|if(d)g(id)∑in∑d|if(d)g(id)

=∑nd∑ndif(d)g(i)∑dn∑indf(d)g(i)

=∑ndf(d)∑ndig(i)∑dnf(d)∑indg(i)

=∑ndf(d)s(nd)∑dnf(d)s(nd)

f(1)s(n)=前缀和-∑nd=2f(d)s(nd)∑d=2nf(d)s(nd)

那么我们就能求s(n)了。所以问题的关键就是构造一个f函数。

容易发现其实这是个恒等变化,即使是非积性函数也同样满足,但是我们比较熟悉的形式是积性函数,所以对于一个函数g我们通常也是先化简成积性函数的形式然后再杜教筛。因为从原形式几乎是无法入手的。

目前比较适用的形式是 μμ的前缀和是1,ϕϕ的前缀和
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: