bzoj 4815: [Cqoi2017]小Q的表格 分块+莫比乌斯反演

题意

小Q是个程序员。
作为一个年轻的程序员,小Q总是被老C欺负,老C经常把一些麻烦的任务交给小Q来处理。每当小Q不知道如何解决时,就只好向你求助。为了完成任务,小Q需要列一个表格,表格有无穷多行,无穷多列,行和列都从1开始标号。为了完成任务,表格里面每个格子都填了一个整数,为了方便描述,小Q把第a行第b列的整数记为f(a,b),为了完成任务,这个表格要满足一些条件:(1)对任意的正整数a,b,都要满足f(a,b)=f(b,a);(2)对任意的正整数a,b,都要满足b×f(a,a+b)=(a+b)*f(a,b)。为了完成任务,一开始表格里面的数很有规律,第a行第b列的数恰好等于a*b,显然一开始是满足上述两个条件的。为了完成任务,小Q需要不断的修改表格里面的数,每当修改了一个格子的数之后,为了让表格继续满足上述两个条件,小Q还需要把这次修改能够波及到的全部格子里都改为恰当的数。由于某种神奇的力量驱使,已经确保了每一轮修改之后所有格子里的数仍然都是整数。为了完成任务,小Q还需要随时获取前k行前k列这个有限区域内所有数的和是多少,答案可能比较大,只需要算出答案mod1,000,000,007之后的结果。
1<=m<=10000,1<=a,b,k<=N<=4*10^6,0<=x<=10^18

分析

要想做这题,首先要知道一个很重要的结论。就是若一个格子(i,j)会被(a,b)影响到,那么当且仅当gcd(i,j)==gcd(a,b).
我们来证明一下结论。
首先根据辗转相减可以得到gcd(a,b)==gcd(a,a+b),也就是说(a,b)能更新到的所有点(i,j)必然都满足gcd(i,j)==gcd(a,b)
再根据性质二,也就是对称性,再结合性质一,可以得到b*f(a+b,a)=(a+b)*f(b,a),也就是若两个点(a,b)和(i,j)可以互相影响,则他们的f值不仅跟横坐标成正比,也跟纵坐标成正比。
那么设gcd(i,j)==d,则必然有f(i,j)=f(d,d)*i*j/d/d
由于f(a,b)的改变必然会导致f(d,d)的改变,f(d,d)的改变必然又会引起所有满足gcd(i,j)==gcd(a,b)的f(i,j)改变,于是就证明完毕了。
那么由于所有的f(a,b)都可以由f(d,d)*a*b/d/d来得到,那么我们就只用记录f(d,d)即可。去掉一维,变为f(d)
那么我们就可以开始愉快的推式子啦!!!
显然

ans=d=1nf(d)i=1nj=1n[gcd(i,j)==d]ijd2

ans=d=1nf(d)i=1ndj=1nd[gcd(i,j)==1]ij

这里有一个很棒的结论就是
i=1nj=1n[gcd(i,j)==1]ij=i=1ni2φ(i)

证明的话可以根据
i=1n[gcd(i,n)==1]i=φ(n)2n

s(n)=i=1nj=1n[gcd(i,j)==1]ij

那么
ans=d=1nf(d)s(nd)

既然可以分块乱搞,那是不是就已经口头AC啦?
但注意到还要资瓷修改。如果用树状数组的话,要询问的复杂度要乘上一个log,显然不资瓷。但注意到修改的复杂度仅仅是一个log,那我们就可以考虑将修改的复杂度增大。
一种很棒的方法是分块,暴力处理修改点所在的块,其余块打标记即可。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;

typedef long long LL;

const int N=4000005;
const int MOD=1000000007;

int n,m,phi[N],tot,prime[N/10],f[N],w[N],tag[N],block,val[N];
bool not_prime[N];

void prework(int n)
{
    phi[1]=1;
    for (int i=2;i<=n;i++)
    {
        if (!not_prime[i]) prime[++tot]=i,phi[i]=i-1;
        for (int j=1;j<=tot&&i*prime[j]<=n;j++)
        {
            not_prime[i*prime[j]]=1;
            if (i%prime[j]==0)
            {
                phi[i*prime[j]]=phi[i]*prime[j];
                break;
            }
            phi[i*prime[j]]=phi[i]*(prime[j]-1);
        }
    }
    for (int i=1;i<=n;i++) f[i]=(f[i-1]+(LL)i*i%MOD*phi[i]%MOD)%MOD;
    for (int i=1;i<=n;i++) w[i]=(w[i-1]+(LL)i*i%MOD)%MOD,val[i]=(LL)i*i%MOD;
}

int gcd(int x,int y)
{
    if (!y) return x;
    else return gcd(y,x%y);
}

int solve(int n)
{
    int ans=0;
    for (int i=1,last;i<=n;i=last+1)
    {
        last=n/(n/i);
        ans=(ans+(LL)(w[last]+tag[(last+block-1)/block]-w[i-1]-tag[(i+block-2)/block])*f[n/i]%MOD)%MOD;
    }
    return (ans+MOD)%MOD;
}

int main()
{
    scanf("%d%d",&m,&n);
    block=sqrt(n);
    prework(n);
    while (m--)
    {
        int a,b,y;LL x;
        scanf("%d%d%lld%d",&a,&b,&x,&y);
        int c=gcd(a,b),v=(LL)x/(a/c)/(b/c)%MOD,u=val[c];val[c]=v;
        for (int i=c;i<=(c+block-1)/block*block;i++) w[i]=(w[i]+v-u)%MOD,w[i]=(w[i]+MOD)%MOD;
        for (int i=(c+block-1)/block+1;i<=(n+block-1)/block;i++) tag[i]=(tag[i]+v-u)%MOD,tag[i]=(tag[i]+MOD)%MOD;
        printf("%d\n",solve(y));
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值