1. 程式人生 > >【BZOJ4816】數字表格,反演+列舉約數

【BZOJ4816】數字表格,反演+列舉約數

傳送門
思路:
考場上沒怎麼卡常,只有60分,感覺自己宛如一個zz
先說一下60分做法
nm
隨便化出來 d=1nf(d)S(nd,md)
其中 S(n,m)=i=1nj=1m[gcd(i,j)=1]=d=1nμ(d)ndmd


O(T(n+m)34) ,帶有4倍的常數
常數十分優秀的話可以卡過去
正解的想法很像於神之怒加強版(我還做過。。。)
把S帶進去

d=1nf(d)ndk=1μ(k)ndkmdk
如果我們把 dk
相同的看做一起的話(也就是當做新的變數 x ),那麼實際上就是一個列舉 x 的約數的過程
x=1ng(x)nxmx
其中 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;
}