Description

Input

Output

Sample Input
输入1:
2 1048
输入2:
4 31415926
Sample Output
输出1:
1
输出2:
942
Data Constraint

分析:
我们设num[i]num[i]num[i]为iii个111组成的数字,显然题目要求∑i=2lnnum[i]\sum_{i=2}^{l}\frac{n}{num[i]}∑i=2lnum[i]n的值,其中除为整除。
对式子进行转化得到,
∑i=2lnnum[i]=∑i=2l9n10i−1\sum_{i=2}^{l}\frac{n}{num[i]}=\sum_{i=2}^{l}\frac{9n}{10^i-1}i=2∑lnum[i]n=i=2∑l10i−19n
其中,我们令N=a∗10d+bN=a*10^d+bN=a∗10d+b,其中b<10db<10^db<10d。
N10d−1=a∗10d+b10d−1=a∗(10d−1)+a+b10d−1=a+a10d−1+b10d−1\frac{N}{10^d-1}=\frac{a*10^d+b}{10^d-1}=\frac{a*(10^d-1)+a+b}{10^d-1}=a+\frac{a}{10^d-1}+\frac{b}{10^d-1}10d−1N=10d−1a∗10d+b=10d−1a∗(10d−1)+a+b=a+10d−1a+10d−1b
其中,a10d−1\frac{a}{10^d-1}10d−1a可以递归下去。相当于每次操作使得NNN减掉最低的ddd位。
我们先不考虑求bbb的部分。对于某一个的ddd,这个数的贡献相当于去掉ddd位,去掉2∗d2*d2∗d位……后的数求和。
假设原来的数中的第iii位,去掉若干位后是新数中的第jjj位,那么显然有(i−j) mod d=0(i-j)\ mod\ d=0(i−j) mod d=0。贡献的次数为i−ji-ji−j的大于111的约数个数,设为d[i−j]d[i-j]d[i−j]。
设f[i]f[i]f[i]为原数中第iii位的贡献和,有
f[i]=a[i]∗∑j=1i10j−1∗d[i−j]f[i]=a[i]*\sum_{j=1}^{i}10^{j-1}*d[i-j]f[i]=a[i]∗j=1∑i10j−1∗d[i−j]
因为10j10^j10j太大,考虑换一种设法,设f[j]f[j]f[j]为新数中第jjj位的贡献和,有
f[j]=10j−1∗∑i=1na[i]∗d[i−j]f[j]=10^{j-1}*\sum_{i=1}^{n}a[i]*d[i-j]f[j]=10j−1∗i=1∑na[i]∗d[i−j]
这是一个差卷积,把aaa翻转后FFT然后再把fff翻转。注意到前面有10j−110^{j-1}10j−1,所以求和相当于把fff的每一位依次输出。
考虑bbb的部分,相当于每ddd位分一组的数相加。数据是随机的,因为b<=10d−1b<=10^d-1b<=10d−1,所以每一组的贡献∈[0,1]\in[0,1]∈[0,1]。而我们只需要保留整数部分,所以我们只要精确到小数点后10位,每一组误差不超过10−1010^{-10}10−10,显然不会影响到整数部分,然后直接求和即可。
因为最后要除以10d−110^d-110d−1,当d>=10d>=10d>=10时,我们同样可以近似处理,直接除以10d10^d10d即可,误差是10−d10^{-d}10−d级的。小的数可以直接暴力求。
复杂度大概是O(10∗l∗logl)O(10*l*logl)O(10∗l∗logl)。
代码:
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define LL long long
const int maxn=1e6+7;
const double pi=acos(-1);
const LL mod=4294967296;
using namespace std;
int n,len;
int r[maxn];
LL S,cnt;
LL a[maxn],b[maxn],c[maxn],d[maxn],f[maxn],s[maxn],bit[maxn];
struct rec{
double x,y;
}dft[maxn],idft[maxn],w[maxn];
rec operator +(rec a,rec b)
{
return (rec){a.x+b.x,a.y+b.y};
}
rec operator -(rec a,rec b)
{
return (rec){a.x-b.x,a.y-b.y};
}
rec operator *(rec a,rec b)
{
return (rec){a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x};
}
rec operator !(rec a)
{
return (rec){a.x,-a.y};
}
void prework()
{
for (int i=1;i<=n;i++) //处理出读入数据,可以近似看做随机
{
a[i]=s[i-1]/1024%10;
s[i]=(s[i-1]*(LL)747796405%mod+mod-1403630843)%mod;
}
reverse(a+1,a+n+1); //输入是从高位向低位编号,先把他反过来
int tmp=0;
for (int i=1;i<=n;i++) //对这个数乘9
{
int last=tmp;
tmp=(a[i]*9+last)/10;
a[i]=(a[i]*9+last)%10;
}
if (tmp) a[++n]=tmp;
reverse(a+1,a+n+1); //重新反回去,做FFT
for (int i=2;i<=n;i++) //预处理d[i]
{
for (int j=i;j<=n;j+=i) d[j]++;
}
}
void fft(rec *a,LL f)
{
for (LL i=0;i<len;i++)
{
if (i<r[i]) swap(a[i],a[r[i]]);
}
w[0]=(rec){1,0};
for (LL i=2;i<=len;i*=2)
{
rec wn=(rec){cos(2*pi/i),f*sin(2*pi/i)};
for (LL j=i/2;j>=0;j-=2) w[j]=w[j/2];
for (LL j=1;j<i/2;j+=2) w[j]=w[j-1]*wn;
for (LL j=0;j<len;j+=i)
{
for (LL k=0;k<i/2;k++)
{
rec u=a[j+k],v=a[j+k+i/2]*w[k];
a[j+k]=u+v;
a[j+k+i/2]=u-v;
}
}
}
}
void FFT(LL *a,LL *b,LL *c,int n,int m)
{
len=1;
int k=0;
while (len<(n+m)) len*=2,k++;
for (int i=0;i<len;i++)
{
r[i]=(r[i>>1]>>1)|((i&1)<<(k-1));
}
for (int i=0;i<len;i++)
{
if (i<n) dft[i].x=a[i]; else dft[i].x=0;
if (i<m) dft[i].y=b[i]; else dft[i].y=0;
}
fft(dft,1);
for (int i=0;i<len;i++)
{
int j=(len-1)&(len-i);
rec da,db;
da=(dft[i]+(!dft[j]))*(rec){0.5,0};
db=(dft[i]-(!dft[j]))*(rec){0,-0.5};
idft[i]=da*db;
}
fft(idft,-1);
for (int i=0;i<m;i++) c[i]=trunc(idft[i].x/len+0.5);
}
int main()
{
freopen("one.in","r",stdin);
freopen("one.out","w",stdout);
scanf("%d%lld",&n,&s[0]);
prework();
FFT(a,d,f,n+1,n+1); //卷积得出f,然后取反
reverse(f+1,f+n+1);
reverse(a+1,a+n+1); //再次把a反过来
bit[0]=1;
for (int i=1;i<=10;i++) bit[i]=bit[i-1]*10;
for (int i=2;i<=10;i++) //对于小的数,分母近似处理可能会出错,暴力处理
{
S=0;
for (int j=1;j<=n;j+=i)
{
for (int k=1;k<=i;k++) S+=a[j+k-1]*bit[k-1];
}
f[1]+=S/(bit[i]-1);
}
for (int i=11;i<=n;i++)
{
for (int j=1;j<=12;j++) b[j]=0; //记录前面的一些位的和
for (int j=1;j<=n;j+=i)
{
for (int k=1;k<=11;k++) b[k]+=a[j+i-11+k-1]; //求和记录再b中,注意高位在区间的右边
}
for (int j=1;j<=11;j++) //移位
{
b[j+1]+=b[j]/10;
b[j]=b[j]%10;
}
f[1]+=b[12]; //整数部加入到答案中,不同的i之间是相互独立的
}
for (int i=1;i<=n;i++)
{
f[i+1]+=f[i]/10;
f[i]=f[i]%10;
}
int num=n;
while ((!f[num]) && (num>1)) num--;
for (int i=num;i>0;i--) printf("%d",f[i]); //移位后输出f
}
博客围绕一个数学问题展开,要求计算∑i=2lnum[i]n的值。通过对式子转化,利用递归处理,引入贡献次数和约数个数等概念,采用差卷积并结合FFT求解。对于部分情况进行近似处理,最后给出复杂度约为O(10∗l∗logl)的分析。

被折叠的 条评论
为什么被折叠?



