题解 SP34096 【DIVCNTK - Counting Divisors (general)】
shadowice1984 · · 题解
DIVCNT2是杜教筛,DIVCNT3是洲阁筛……
好麻烦啊,不想写qaq……
怎么办啊?
写DIVCNTK然后把k改成2和3就行了啊
使用下面的算法只需要把代码中的k改成2和3就能通过divcnt2,3了,所以这是个3倍经验……
好了让我们看一下这题让我们求什么?
其中
那么我们不难发现函数
解释一下就是我们发现如果p是一个质数那么
看起来好像没啥性质啊,怎么做这题呢?
答案是一个被称为Min_25筛的黑科技,这个东西的复杂度在今年的集训队论文当中刚刚被证明,令人惊讶的是,它的复杂度居然是
其实这个复杂度记号并不标准
那个
然后这并没有什么卵用,这东西的复杂度趋于
好了让我们来看一下这个算法能筛什么积性函数
首先这个函数必须是一个积性函数,
其次我们要求
最后一点就是
那么Min_25筛分为两部分,第一部分是求质数点值,而第二部分就是大力搜索
让我们先来介绍第一部分
Part1:求所有质数点值的和
众所周知给定一个
求出一个函数
那么我们怎么做呢,容易发现当i是质数的时候
那么我们可以设计一个dp,不妨令
1.i是一个小于等于n的质数
2.i是一个小于等于n的非质数,并且i的最小质因子大于
那么我们发现一个比较显然的事实是
为什么呢?因为我们发现对于第二类i来讲,最小的i应该是
如果很不巧,我们的
为了式子的美观我直接省略了下取整符号
为什么这个式子是对的呢?
我们发现
i是一个小于等于n的非质数并且i的最小质因子恰好等于
那么由于
那么我们预处理一下
直接暴力的实现这个dp是
但是我们发现一件事情是如果我们用滚动数组实现这个dp的话,当
好了这样dp到最后的话我们就求出了
边界条件
注意一点不要拿map或者hash表来存,而是对于小于
对于这题来讲我们发现
Part2 筛积性函数前缀和
怎么筛呢?
暴力dfs,对你没看错,就是暴 力 搜
我们设
那么怎么计算
质数的贡献显然相当好算,就是
而合数部分的贡献就是暴力递归,大力枚举最小质因子的值以及次幂,我们可以得到这样一个式子
解释一下就是把最小质因子全部除掉然后直接递归下去算,由于
好了接下来是更加玄学的地方,实现这个东西,不 需 要 记 忆 化
直接暴力搜索即可,然后也不难理解为什么这个东西会变成近线性复杂度了,但是这东西就是快,你没办法……
然后对于刚才的式子我们直接把
备注:把这份代码的K改成2和3可以直接通过DIVCNT2和3
上代码~
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;const int N=1e5+1000;typedef unsigned long long ll;
int zhi[N];int ct;bool book[N];int lim;ll n;ll K;ll q[N];int hd;int T;
ll dp1[N];ll dp2[N];ll pre[N];
inline ll solve(ll p,int q)//灵魂搜索代码
{
if(p<zhi[q])return 0;ll ret=((p<=lim)?dp1[p]:dp2[n/p])-pre[q-1];
if(q==ct+1)return (p<=(ll)zhi[ct])?0:ret;ll pw;int t;
for(int k=q;k<=ct&&(ll)zhi[k]*zhi[k]<=p;k++)
for(pw=zhi[k],t=1;pw<=p;pw*=zhi[k],t++)
ret+=(K*t+1)*(solve(p/pw,k+1)+(t>1));return ret;
}
inline void calc1()//dp预处理
{
for(int i=1;i<=lim;i++)dp1[i]=i-1;for(int i=1;i<=hd;i++)dp2[i]=q[i]-1;
for(int j=1,t1=hd;j<=ct;j++)
{
ll lt=(ll)zhi[j]*zhi[j];while(lt>q[t1]&&t1>=1)t1--;
for(int i=1;i<=t1;i++)
{ll fv=q[i]/zhi[j];fv=(fv<=lim)?dp1[fv]:dp2[n/fv];dp2[i]-=fv-j+1;}
for(int i=lim;i>=lt;i--)dp1[i]-=dp1[i/zhi[j]]-j+1;
}for(int i=1;i<=lim;i++)dp1[i]=(K+1)*dp1[i];
for(int i=1;i<=hd;i++)dp2[i]=(K+1)*dp2[i];
}
inline void solve()
{
scanf("%llu%llu",&n,&K);lim=sqrt(n);for(ct=1;(ll)zhi[ct]<=lim;ct++);ct--;hd=0;
for(ll i=1,r;i<=n;i=r+1){r=n/i;if(r>lim)q[++hd]=r,r=n/r;else break;}
for(int i=1;i<=ct;i++)pre[i]=(K+1)*i;calc1();
printf("%llu\n",solve(n,1)+1);
}
int main()
{
for(int i=2;i<=N-10;i++)//线性筛素数
{
if(!book[i])zhi[++ct]=i;
for(int j=1;j<=ct&&(ll)i*zhi[j]<=N-10;j++)
{book[i*zhi[j]]=true;if(i%zhi[j]==0)break;}
}scanf("%d",&T);for(int z=1;z<=T;z++)solve();return 0;//拜拜程序~
}