【BZOJ4815】小Q的表格(CQOI2017)-数论+分块

测试地址:小Q的表格
做法:本题需要用到数论+分块。
这道题可真是个神题……之所以标题笼统地写了数论,是因为它用到的知识实在是有点多……这里给大家,也算给自己理一遍思路,以免忘了。
首先分析题目所给二元函数的条件:
f(a,b)=f(b,a) f ( a , b ) = f ( b , a )
bf(a,a+b)=(a+b)f(a,b) b ⋅ f ( a , a + b ) = ( a + b ) ⋅ f ( a , b )
将第二个式子中的 b b 换成ba,得到: (ba)f(a,b)=bf(a,ba) ( b − a ) ⋅ f ( a , b ) = b ⋅ f ( a , b − a )
变换一下形式,得到: f(a,b)=bbaf(a,ba) f ( a , b ) = b b − a ⋅ f ( a , b − a )
这个东西有什么用呢?我们继续得到: f(a,ba)=bab2af(a,b2a) f ( a , b − a ) = b − a b − 2 a ⋅ f ( a , b − 2 a )
于是有: f(a,b)=bb2af(a,b2a) f ( a , b ) = b b − 2 a ⋅ f ( a , b − 2 a )
观察这个式子,容易用数学归纳法得出 f(a,b)=bbmodaf(a,bmoda) f ( a , b ) = b b mod a ⋅ f ( a , b mod a ) 的结论。注意由于 a=0 a = 0 b=0 b = 0 f(a,b) f ( a , b ) 没有意义,这里若 bmoda=0 b mod a = 0 ,我们就把它的值当成 a a ,下面都用这样的方式进行表示。
又由于f(a,b)=f(b,a),我们可以把 a a bmoda换过来再进行一遍这样的过程。我们不断这样换来换去,发现两个数的变动和辗转相除法中两个数的变化非常相似,那么令 d=gcd(a,b) d = gcd ( a , b ) ,最后我们可以得到 f(a,b)=xf(d,d) f ( a , b ) = x ⋅ f ( d , d ) ,因此我们知道,只要确定了 f(d,d) f ( d , d ) ,整个表格就随之确定了。于是现在的问题是,系数 x x 是个什么东西。
我们知道f(a,b)=bbmodaf(a,bmoda),把右边 f f 中的两项换过来再求一遍,得到:
f(a,b)=bbmodaaamod(bmoda)f(bmoda,amod(bmoda))
如果再往下算一步,系数会乘上一个分子为 bmoda b mod a 的分数,和 bbmoda b b mod a 的分母相互抵消,那么我们就得到了一个新的分母,太长就不写出来了,但一定和 f f 中的一项相同。于是我们发现,随着等式右边两项变化成x,y,系数会同时变化成 axby a x ⋅ b y ,因此当最后 x=y=d x = y = d 时,有 f(a,b)=adbdf(d,d) f ( a , b ) = a d ⋅ b d ⋅ f ( d , d ) ,这样我们就求出了系数。
推导了上面一大堆东西之后,修改操作就很显然了,把所有修改都变成针对 f(d,d) f ( d , d ) 的修改即可。现在主要是询问操作,我们要求:
ans=ni=1nj=1f(i,j) a n s = ∑ i = 1 n ∑ j = 1 n f ( i , j )
显然把上面算出的式子带进去,把 d d 提出来得:
ans=d=1nf(d,d)i=1ndj=1nd[gcd(i,j)=1]ij
g(n)=ni=1nj=1[gcd(i,j)=1]ij g ( n ) = ∑ i = 1 n ∑ j = 1 n [ gcd ( i , j ) = 1 ] i j ,显然式子就变成了一个数论分块的形式,那么现在有两个问题要处理:
首先我们要解决快速求 f(d,d) f ( d , d ) 前缀和的问题。因为询问一共有 mn m n 次,我们不能承受 O(logn) O ( log ⁡ n ) 的询问复杂度,怎么办呢?注意到修改只有 m m 次,所以我们分块,对每个点维护块内前缀和,再对每个块维护块的前缀和,即可做到修改O(n),询问 O(1) O ( 1 ) 的复杂度。
然后我们要解决预处理 g(n) g ( n ) 的问题。我们发现直接求这个东西非常不好求,怎么想都是 O(n) O ( n ) 的复杂度,于是我们考虑递推,即考虑 g(n) g ( n ) g(n1) g ( n − 1 ) 相比多了什么。
显然,它多出的贡献是 2nni=1[gcd(i,n)=1]i 2 n ∑ i = 1 n [ gcd ( i , n ) = 1 ] i 。注意当 n=1 n = 1 时不能这样求,因为对于 n>1 n > 1 gcd(n,n)=n1 gcd ( n , n ) = n ≠ 1 ,不会有重复的问题,但当 n=1 n = 1 时就会有算重,特判一下即可。
对于 ni=1[gcd(i,n)=1]i ∑ i = 1 n [ gcd ( i , n ) = 1 ] i 这个式子,它是在算在 n n 之内与n互质的所有数之和。注意到这样的数总是成对出现的,如果 x x n互质,那么 nx n − x 也与 n n 互质,所以我们有:
i=1n[gcd(i,n)=1]i=φ(n)2n
虽然当 n=1 n = 1 时显然不成立,但它带进原式中时恰好把原来的特判给省掉了,于是我们找到了递推式:
g(n)=g(n1)+n2φ(n) g ( n ) = g ( n − 1 ) + n 2 φ ( n )
注意到 n2φ(n) n 2 φ ( n ) 是积性函数,线性筛预处理即可,时间复杂度为 O(n) O ( n )
经过了上面一系列漫长的讨论,我们终于完全地解决了这个问题,时间复杂度为 O(mn) O ( m n )
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1000000007;
int m;
ll n,blocksiz,prime[2000010],g[4000010];
ll val[4000010],sum[4000010],blocksum[10010];
bool vis[4000010]={0};

void calc_g()
{
    prime[0]=0;
    g[0]=0,g[1]=1;
    for(ll i=2;i<=n;i++)
    {
        if (!vis[i])
        {
            prime[++prime[0]]=i;
            g[i]=i*i%mod*(i-1)%mod;
        }
        for(int j=1;j<=prime[0]&&i*prime[j]<=n;j++)
        {
            vis[i*prime[j]]=1;
            if (i%prime[j]==0)
            {
                g[i*prime[j]]=g[i]*prime[j]%mod*prime[j]%mod*prime[j]%mod;
                break;
            }
            g[i*prime[j]]=g[i]*prime[j]%mod*prime[j]%mod*(prime[j]-1)%mod;
        }
    }
    for(ll i=1;i<=n;i++)
        g[i]=(g[i]+g[i-1])%mod;
}

void init()
{
    scanf("%d%lld",&m,&n);

    blocksiz=sqrt(n);
    blocksum[0]=0;
    for(ll i=1,nowblock=0;i<=n;i++)
    {
        val[i]=i*i%mod;
        if (i%blocksiz) sum[i]=(sum[i-1]+val[i])%mod;
        else sum[i]=val[i];
        if (i%blocksiz==blocksiz-1)
            blocksum[(i+1)/blocksiz]=(blocksum[i/blocksiz]+sum[i])%mod;
    }
    calc_g();
}

ll gcd(ll a,ll b)
{
    return b?gcd(b,a%b):a;
}

void modify(ll pos,ll x)
{
    ll add=x-val[pos];
    val[pos]=x;
    for(int i=pos;;i++)
    {
        sum[i]+=add;
        if (i%blocksiz==blocksiz-1) break;
    }
    for(int i=pos/blocksiz+1;i<=n/blocksiz+1;i++)
        blocksum[i]+=add;
}

ll query(ll l,ll r)
{
    l--;
    ll ans=0;
    if (l>0) ans=ans-blocksum[l/blocksiz]-sum[l];
    ans=ans+blocksum[r/blocksiz]+sum[r];
    ans=(ans%mod+mod)%mod;
    return ans;
}

void work()
{
    ll a,b,k,x;
    for(int i=1;i<=m;i++)
    {
        scanf("%lld%lld%lld%lld",&a,&b,&x,&k);

        ll d=gcd(a,b);
        x/=a/d*b/d;
        modify(d,x);

        ll ans=0;
        for(ll i=k;i>=1;i=k/(k/i+1))
        {
            ll l=k/(k/i+1)+1,r=i;
            ans=(ans+query(l,r)*g[k/i])%mod;
        }
        printf("%lld\n",ans);
    }
}

int main()
{
    init();
    work();

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值