BZOJ4815: [Cqoi2017]小Q的表格

BZOJ4815

b×f(a,a+b)=(a+b)f(a,b) 很像辗转相减法。。
那么每次修改点 (a,b) 的值,会修改所有满足 gcd(i,j)==gcd(a,b) 的点 (i,j) 的值。
d=gcd(a,b) ,那么 fi,j=xi/dj/d (gcd(i,j)==d

那么可以转化为,每次修改对角线上的值。记 Numi fi,i

ans=d=1nNumdi=1nj=1n[gcd(i,j)==d]i/dj/d

ans=d=1nNumdI=1n/dj=1n/d[gcd(i,j)==1]ij

G(n)=nI=1nj=1[gcd(i,j)==1]ij
根据 BZOJ2154: Crash的数字表格 的做法
G(n)=i=1nμ(i)i2S2(n/i)

S(i)=i(i+1)2

nn G(n) 无法接受,复杂度还是高了一些。
考虑递推。当且仅当 n|d 时, nd n1d 大1
那么 G(n)=G(n1)+i|nμ(i)i2(S2(ni)S2(n1i)
H(n)=i|nμ(i)i2(S2(ni)S2(n1i)=n3i|nμ(i)1i
h(n)=i|nμ(i)1i
发现 h(n) 是一个积性函数。
证明一下:
对于两个互质的数 x,y
x 的因子集为A,大小为 s1
y 的因子集为B,大小为 s2
那么 xy 的因子集 C AB,大小为 s1s2
举个例子 x=10,y=21
A=1,2,5,10  s1=4
B=1,3,7,21  s2=4
那么
C=1372126144251535105103070210

显然 μ(a)1aμ(b)1b=μ(ab)1ab
那么相当于 h(xy) A 中每个μ(a)1a乘以 B 中每个μ(b)1b
然后就可以线性筛出 h(n)

ans=d=1nNumdG(n/d)

然后就可以下底函数分块来求了。但是有修改操作,如果用树状数组,修改复杂度为 O(logn) ,但是一次查询的复杂度为 O(lognn) ,会 T 掉。
那么可以考虑牺牲一下修改的复杂度,用分块来维护前缀和,修改变成O(n),查询变成 O(1) 了。

然后这个题终!于!做!完!了!
19s 算是给卡过去了。。好像 S2(n)=ni=1φ(i)i2 但是不理解为什么啊 QAQ ,如有神犇愿意解答感激不尽!

【代码】

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>
#define N 4000005
#define M 600005
#define Mod 1000000007
#define INF 0x7fffffff
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const ull base=31;

ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return x*f;
}

int m,n,block,Mx;
int Prime[N],bl[N],R[N];bool Not_Prime[N]={1,1};
ll f[N],h[N],g[N],Inv[N],sum[2005][2005],Sum[2005];

ll SSqr(int x){
    return 1LL*x*x%Mod*x%Mod;
}

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

void Pre()
{
    Inv[1]=h[1]=g[1]=1;block=sqrt(m);
    for(register int i=2;i<=m;i++)
    {
        Inv[i]=(Mod-Mod/i)*Inv[Mod%i]%Mod;
        if(!Not_Prime[i]) Prime[++Prime[0]]=i,h[i]=(1-Inv[i]+Mod)%Mod;
        for(register int j=1;j<=Prime[0]&&i*Prime[j]<=m;j++)
        {
            Not_Prime[i*Prime[j]]=1;
            if(i%Prime[j]==0) {
                h[i*Prime[j]]=h[i];
                break;
            }
            h[i*Prime[j]]=h[i]*h[Prime[j]]%Mod;
        }
        g[i]=(g[i-1]+SSqr(i)*h[i]%Mod)%Mod;
    }
    for(register int i=1;i<=m;i++) {
        f[i]=1LL*i*i;
        bl[i]=(i-1)/block+1;
        Sum[bl[i]]=(Sum[bl[i]]+f[i])%Mod;
        sum[bl[i]][i-R[bl[i]-1]]=(sum[bl[i]][i-R[bl[i]-1]-1]+f[i])%Mod;
        if(i==n||(i%block==0)) R[bl[i]]=i;
    } 
    Mx=bl[m];
    for(int i=1;i<=Mx;i++)
        Sum[i]=(Sum[i]+Sum[i-1])%Mod;
}

void Update(ll d)
{
    int Bl=bl[d];
    for(int i=d;i<=R[Bl];i++)
        sum[Bl][i-R[Bl-1]]=(sum[Bl][i-1-R[Bl-1]]+f[i])%Mod;
    Sum[Bl]=(Sum[Bl-1]+sum[Bl][R[Bl]-R[Bl-1]])%Mod;
    for(int i=Bl+1;i<=Mx;i++)
        Sum[i]=Sum[i-1]+sum[i][R[i]-R[i-1]];
}

ll Get_Sum(int x,int y)
{
    int sx=bl[x],sy=bl[y];
    if(sx==sy) return (sum[sx][y-R[sx-1]]-sum[sx][x-1-R[sx-1]]+Mod)%Mod;
    return ((sum[sx][R[sx]-R[sx-1]]-sum[sx][x-1-R[sx-1]]+sum[sy][y-R[sy-1]]+Sum[sy-1]-Sum[sx])%Mod+Mod)%Mod;
}

void Solve(int x)
{
    int pos;
    ll ans=0;
    for(int i=1;i<=x;i=pos+1)
    {
        pos=(x/(x/i));
        ans=(ans+Get_Sum(i,pos)*g[x/i]%Mod)%Mod;
    }   
    printf("%lld\n",ans);
}

int main()
{
    n=read(),m=read();
    Pre();
    for(int i=1;i<=n;i++)
    {
        static int a,b,k;ll x;
        a=read(),b=read(),x=read(),k=read();x%=Mod;
        int GCD=gcd(a,b);
        f[GCD]=x*Inv[a/GCD]%Mod*Inv[b/GCD]%Mod;
        Update(GCD);
        Solve(k);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值