jzoj 6065.【NOI2019模拟2019.3.18】One? One! fft

博客围绕一个数学问题展开,要求计算∑i=2lnum[i]n的值。通过对式子转化,利用递归处理,引入贡献次数和约数个数等概念,采用差卷积并结合FFT求解。对于部分情况进行近似处理,最后给出复杂度约为O(10∗l∗logl)的分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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]iii111组成的数字,显然题目要求∑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=2lnum[i]n=i=2l10i19n

其中,我们令N=a∗10d+bN=a*10^d+bN=a10d+b,其中b&lt;10db&lt;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}10d1N=10d1a10d+b=10d1a(10d1)+a+b=a+10d1a+10d1b
其中,a10d−1\frac{a}{10^d-1}10d1a可以递归下去。相当于每次操作使得NNN减掉最低的ddd位。
我们先不考虑求bbb的部分。对于某一个的ddd,这个数的贡献相当于去掉ddd位,去掉2∗d2*d2d位……后的数求和。
假设原来的数中的第iii位,去掉若干位后是新数中的第jjj位,那么显然有(i−j) mod d=0(i-j)\ mod\ d=0(ij) mod d=0。贡献的次数为i−ji-jij的大于111的约数个数,设为d[i−j]d[i-j]d[ij]
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=1i10j1d[ij]
因为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]=10j1i=1na[i]d[ij]
这是一个差卷积,把aaa翻转后FFT然后再把fff翻转。注意到前面有10j−110^{j-1}10j1,所以求和相当于把fff的每一位依次输出。

考虑bbb的部分,相当于每ddd位分一组的数相加。数据是随机的,因为b&lt;=10d−1b&lt;=10^d-1b<=10d1,所以每一组的贡献∈[0,1]\in[0,1][0,1]。而我们只需要保留整数部分,所以我们只要精确到小数点后10位,每一组误差不超过10−1010^{-10}1010,显然不会影响到整数部分,然后直接求和即可。

因为最后要除以10d−110^d-110d1,当d&gt;=10d&gt;=10d>=10时,我们同样可以近似处理,直接除以10d10^d10d即可,误差是10−d10^{-d}10d级的。小的数可以直接暴力求。

复杂度大概是O(10∗l∗logl)O(10*l*logl)O(10llogl)

代码:

#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
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值