【CQOI2017】bzoj4815 小Q的表格

229 篇文章 0 订阅
148 篇文章 0 订阅

根据辗转相减可以知道,相互影响的就是 gcd 相同的那些数。又根据条件 2 可以知道,所有gcd相同的数的比例是不会改变的,又因为最开始 a(x,y)=xy 是一组解,因此每次操作都相当于把所有 gcd 相同的数修改为 a(x,y)=kxy 。因此,对于修改我们只需要维护 f(d) 表示对于所有 gcd(x,y)=d (x,y) a(x,y)=f(d)xy
接下来考虑询问,也就是求

====i=1nj=1nf(gcd(i,j))ijd=1nf(d)i=1nj=1nij[gcd(i,j)=d]d=1nf(d)d2i=1ndj=1ndije(gcd(i,j))d=1nf(d)d2i=1ndj=1ndijx|i,x|jμ(x)d=1nf(d)d2x=1ndμ(x)x2i=1ndxj=1ndxij


s(n)=i=1ni=n(n+1)2

g(n)=i=1nμ(i)i2s2(ni)

如果能够预处理出 g ,就可以下底函数分块来求解原问题。但是如果直接依次求是O(nn)的,无法接受。考虑递推。注意到 nd n1d 1 ,当且仅当d|n,也就是 n 除以d余数为零。因此可以得到
g(n)=g(n1)+i|nμ(i)i2(s2(ni)s2(ni1))=g(n1)+n3i|nμ(i)1i

其中 h(n)=i|nμ(i)1i 为积性函数可以线性筛,于是我们就可以 O(n) 预处理出 g
还有一个问题,在对答案进行下底函数分块的时候需要维护f(i)i2的前缀和,如果直接用树状数组的话,修改是 O(logn) 的,询问是 O(nlogn) 的,无法接受。因此可以分块,牺牲修改的复杂度来降低询问的复杂度,单次修改 O(n) ,单次询问 O(1) 。这样最终的复杂度是 O(n+mn)
好像有一点卡常,考虑到比较小的 d=gcd(x,y) 被调用的次数比较多,可以在分块的时候维护后缀和,这样修改次数可以减少。

#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
#define LL long long
const int p=1000000007,maxn=4000010,maxT=2010;
LL f[maxn],g[maxn],h[maxn],inv[maxn],
sum[maxT],sumin[maxT][maxT];
int prm[maxn],have[maxn],bel[maxn],L[maxT],R[maxT],
q,n,T,tot;
int gcd(int x,int y)
{
    return y?gcd(y,x%y):x;
}
void pause()
{
    int x;
    x=1;
}
void init()
{
    inv[1]=g[1]=h[1]=1;
    for (int i=2;i<=n;i++)
    {
        inv[i]=(p-inv[p%i])*(p/i)%p;
        if (!have[i])
        {
            prm[++tot]=i;
            h[i]=(1-inv[i]+p)%p;
        }
        for (int j=1;j<=tot&&(LL)i*prm[j]<=n;j++)
        {
            have[i*prm[j]]=1;
            if (i%prm[j]==0)
            {
                h[i*prm[j]]=h[i];
                break;
            }
            else h[i*prm[j]]=h[i]*h[prm[j]]%p;
        }
        g[i]=(g[i-1]+(LL)i*i%p*i%p*h[i])%p;
    }
    T=sqrt(n);
    for (int i=1;i<=T;i++)
    {
        L[i]=R[i-1]+1;
        R[i]=i==T?n:L[i]+T-1;
        for (int j=L[i];j<=R[i];j++)
        {
            f[j]=1;
            bel[j]=i;
            sumin[i][j-L[i]+1]=(sumin[i][j-L[i]]+(LL)j*j)%p;
        }
    }
    for (int i=T;i;i--)
        sum[i]=(sum[i+1]+sumin[i][R[i]-L[i]+1])%p;
}
LL getsum(int l,int r)
{
    //if (l==10&&r==13) pause();
    //LL ret1;
    int x=bel[l],y=bel[r];
    if (x==y) return (sumin[x][r-L[x]+1]-sumin[x][l-L[x]]+p)%p;
    return (sum[x+1]-sum[y]+p+sumin[y][r-L[y]+1]+sumin[x][R[x]-L[x]+1]-sumin[x][l-L[x]]+p)%p;
    /*LL ret=0;
    for (int i=l;i<=r;i++) ret=(ret+f[i]*i%p*i%p)%p;
    if (ret!=ret1) pause();
    return ret;*/
}
void update(int d)
{
    for (int j=d;j<=R[bel[d]];j++)
        sumin[bel[d]][j-L[bel[d]]+1]=(sumin[bel[d]][j-L[bel[d]]]+f[j]*j%p*j)%p;
    for (int j=bel[d];j;j--)
        sum[j]=(sum[j+1]+sumin[j][R[j]-L[j]+1])%p;
}
void solve(int m)
{
    LL ans=0;
    int u;
    for (int j=1;j<=m;j=u+1)
    {
        u=m/(m/j);
        ans=(ans+getsum(j,u)*g[m/j])%p;
    }
    printf("%lld\n",ans);
}
int main()
{
    /*freopen("table.in","r",stdin);
    freopen("table.out","w",stdout);*/
    int xx,yy,m,d;
    LL x;
    scanf("%d%d",&q,&n);
    init();
    while (q--)
    {
        scanf("%d%d%lld%d",&xx,&yy,&x,&m);
        x%=p;
        d=gcd(xx,yy);
        f[d]=(LL)x*inv[xx]%p*inv[yy]%p;
        update(d);
        solve(m);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值