[JZOJ5539] psy

Description

有很多n位数(可以有前导0),如果一个n位数X对所有的 k(1k<n) 都满足
X10kMod10n>X ,这个X我们就认为它脱团了。现在告诉你n,求出有多少个X脱团了。
题目是这样的,设f(n)是n位数里脱团数的数量(脱团数定义如上),现在让你求出f(1)*1^2+f(2)*2^2+…+f(i)*i^2+…+f(n)*n^2 mod 1000000007的结果。
对于100%的数据:n≤10000000000。
所有的一位数都满足,所以f(1)=10。
满足条件的两位数的有01到09,12到19,23到29,34到39,45到49,56到59,67到69,78,79,89总共45个。

Solution

考虑f(n)怎么求

发现它是类似字符串周期的东西
先将这个数首尾相连拼成一个环

可以发现如果循环节长度小于n那么一定不满足(因为从第二个循环节开头开始前面的都一样,但是后面的是0)

那么剩下的串都是循环节长度等于n的(即从哪个位置开头都不一样)

然后我们发现循环的n个字符串中只有字典序最小那个符合要求

那么只需要求出循环节长度为n的,再除以n即为所求f

可以发现如果存在循环节长度为i,那么i的倍数一定也是循环节

每个质因子只用算一次
容斥原理
所以

f(n)=d|nμ(d)10ndn

那么答案就是

i=1nid|iμ(d)10nd

交换主体

=d=1dμ(d)i=1ndi10i

前面的可以杜教筛来做,具体可以看我的Blog

后面的话,我的SB做法是分治,分成n/2前后两部分处理
然而如果你高中数学过关的话可以用类似等比数列的方法推出来(10S-S)之类的,然后裂项相消

最后推出来是

10n+1n10n+11009109

懒得化简了

复杂度大概是 O(N23+NlogN)

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
#define N 5000005
#define LL long long
#define mo 1000000007
#define H 1000007
using namespace std;
LL n,ny,ny9;
int h[N],su[N],mu[N],pr[N],wh[H];
bool bz[N];
void prp()
{
    mu[1]=su[1]=1;
    fo(i,2,5000000)
    {
        if(!bz[i])
        {
            pr[++pr[0]]=i;
            mu[i]=-1;
        }
        for(int j=1;j<=pr[0]&&i*pr[j]<=5000000;j++)
        {
            bz[i*pr[j]]=1;
            if(i%pr[j]==0) 
            {
                mu[i*pr[j]]=0;
                break;
            }
            mu[i*pr[j]]=-mu[i];
        }
        su[i]=((LL)su[i-1]+(LL)i*mu[i])%mo;
    }
} 
int hs(LL k)
{
    int w=k%H;
    while(wh[w]!=k&&wh[w]!=0) w=(w+1)%H;
    return w;
}
LL get(LL k)
{
    if(k<=5000000) return su[k];
    int p=hs(k);
    if(wh[p]==k) return h[p];
    wh[p]=k,h[p]=1;
    LL d=2;
    while(d<=k)
    {
        LL m=k/d,d1=k/m;
        h[p]=((LL)h[p]-(d+d1)%mo*((d1-d+1)%mo)%mo*ny%mo*get(m)%mo+mo)%mo;
        d=d1+1;
    }
    return h[p];
}
LL ksm(LL k,LL n)
{
    LL s=1;
    k=k%mo;
    for(;n;k=k*k%mo,n>>=1) if(n&1) s=s*k%mo;
    return s;
}
LL doit(LL n)
{
    LL ms1=ksm(10,n+1);
    return(n%mo*ms1%mo-(ms1-100)%mo*ny9%mo-10+mo)%mo*ny9%mo;
}
int main()
{
    LL ans=0;
    cin>>n;
    ny=ksm(2,mo-2);
    ny9=ksm(9,mo-2);
    LL d=1;
    prp();
    while(d<=n)
    {
        LL m=n/d,d1=n/m,s=0;
        ans=(ans+(get(d1)-get(d-1)+mo)%mo*doit(m)%mo+mo)%mo;
        d=d1+1;
    }   
    printf("%lld\n",(ans+mo)%mo);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值