您的位置:首页 > 其它

【BZOJ4816】数字表格,反演+枚举约数

2017-04-12 08:07 344 查看
传送门

思路:

考场上没怎么卡常,只有60分,感觉自己宛如一个zz

先说一下60分做法

设n≤m

随便化出来∏d=1nf(d)S(⌊nd⌋,⌊md⌋)

其中S(n,m)=∑i=1n∑j=1m[gcd(i,j)=1]=∑d=1nμ(d)⌊nd⌋⌊md⌋

O(T(n+m)34),带有4倍的常数

常数十分优秀的话可以卡过去

正解的想法很像于神之怒加强版(我还做过。。。)

把S带进去

∏d=1nf(d)∑⌊nd⌋k=1μ(k)⌊ndk⌋⌊mdk⌋

如果我们把dk相同的看做一起的话(也就是当做新的变量x),那么实际上就是一个枚举x的约数的过程

∏x=1ng(x)⌊nx⌋⌊mx⌋

其中g(n)=∏d|nf(d)μ(nd)

非常像狄利克雷卷积形式,但并不是,而且这也不是积性函数(显然)

所以用筛倍数的方法求得g(n),复杂度O(nlnn),同时还要处理f(n)的逆元,g(n)的前缀积和逆元

处理询问时的复杂度是O(T(n√+m−−√)logn)

处理逆元时可以O(n)做

#include<cstdio>
#include<iostream>
#define mo 1000000007
using namespace std;
int n,m;
const int lim=1000000;
int prime[lim/10+5],mu[lim+5];
int sf[lim+5],f[lim+5],invf[lim+5],sg[lim+5],g[lim+5],invg[lim+5];
bool vis[lim+5];
int qr(int x,int y)
{
int t=1;
for (;y;y>>=1,x=1LL*x*x%mo)
if (y&1) t=1LL*t*x%mo;
return t;
}
void init()
{
mu[1]=1;
for (int i=2;i<=lim;++i)
{
if (!vis[i])
prime[++prime[0]]=i,
mu[i]=-1;
for (int j=1;j<=prime[0]&&i*prime[j]<=lim;++j)
{
vis[i*prime[j]]=1;
if (i%prime[j])
mu[i*prime[j]]=-mu[i];
else
{
mu[i*prime[j]]=0;
break;
}
}
}
f[0]=0;
sf[1]=1;
f[1]=1;
g[1]=1;
sg[1]=1;
invg[0]=invg[1]=invf[0]=invf[1]=1;
for (int i=2;i<=lim;++i)
{
f[i]=f[i-1]+f[i-2];
if (f[i]>=mo) f[i]-=mo;
sf[i]=1LL*sf[i-1]*f[i]%mo;
g[i]=1;
}
invf[lim]=qr(sf[lim],mo-2);
for (int i=lim;i>=2;--i)
invf[i-1]=1LL*invf[i]*f[i]%mo,
invf[i]=1LL*invf[i]*sf[i-1]%mo;
for (int i=2;i<=lim;++i)
{
for (int j=1;i*j<=lim;++j)
if (mu[j]==1)
g[i*j]=1LL*g[i*j]*f[i]%mo;
else if (mu[j]==-1)
g[i*j]=1LL*g[i*j]*invf[i]%mo;
sg[i]=1LL*sg[i-1]*g[i]%mo;
}
invg[lim]=qr(sg[lim],mo-2);
for (int i=lim;i>=2;--i) invg[i-1]=1LL*invg[i]*g[i]%mo;
}
void work()
{
scanf("%d%d",&n,&m);
int t;
if (n>m) swap(n,m);
int ans=1;
for (int last,t,i=1;i<=n;i=last+1)
last=min(n/(n/i),m/(m/i)),
ans=1LL*ans*qr(1LL*sg[last]*invg[i-1]%mo,1LL*(n/i)*(m/i)%(mo-1))%mo;
printf("%d\n",ans);
}
int main()
{
init();
int T;
for (scanf("%d",&T);T;--T) work();
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: