题解 P5432 【A/B Problem (高精度除法)】

xenonex

2020-01-25 02:28:42

Solution

居然只有一篇题解 ~~而且不给代码~~ ~~调死我这个退役菜鸡了qaq~~ ------------ **这是一篇C++题解** 这道题目里,神鱼让我求两个$10^{200000}$级别的大数的整除商$\lfloor\frac{a}{b}\rfloor$。显然普通的$O(n^2)$做法不再适用。直接考虑高精度实数方法,计算$\frac{a}{b}$然后取整数部分,先计算$\frac{1}{b}$,然后与$a$相乘。 至于$\frac{1}{b}$的求法与多项式求逆有点类似,也利用迭代法计算。具体地,若已有$x_0$满足 $$bx_0=1-\epsilon$$ 其中$\epsilon$代表一个小于$1$的误差,则有 $$bx_0-1=-\epsilon$$ $$(bx_0-1)^2=\epsilon^2$$ $$b^2x_0^2-2bx_0+1=\epsilon^2$$ $$b(2x_0-bx_0^2)=1-\epsilon^2$$ 所以只要不断迭代: $$x_0\longleftarrow 2x_0-bx_0^2$$ 误差就能平方级地缩小,换言之一次迭代后有效位数是之前的$2$倍,所以完成倒数计算需要$O(\log(\text{精度}))$次迭代。若配合FFT及倍增实现,整个过程就是$O(n\log n)$的。但我们只需要整数部分,所以有效位数取$\log_{10}\frac{a}{b}$位左右就差不多了。 ~~后来发现这就是牛顿迭代~~ 然后……就没了?注意各种细节吧,题目数据挺强的。特别注意算出来的整数部分可能会比真实值少1,微调下就可以了。 之所以主过程用FFT实现而不是NTT是因为中间结果会爆int。 ~~但题目要求求逆输出于是单独写了一个NTT然后代码就5k了~~ ~~高精度存char是为了省空间~~ ~~码风诡异不喜勿喷~~ ```cpp #include<cstdio> #include<cstring> #include<cmath> #define inline __inline__ __attribute__((always_inline)) #define L 524288 #define mod 998244353 typedef long long LL; int rev[L<<1]; namespace NTT //为了输出而写的NTT…… { int W[2][L],_i[20],invtmp[L],x[L],y[L]; inline int invx(int x){int r=1;for(x%=mod;x>1;x=mod%x)r=LL(mod-mod/x)*r%mod;return r;} inline int ksm(LL a,LL b){int r=1;for(;b;a=a*a%mod,b>>=1)if(b&1)r=r*a%mod;return r;} void NTT_Init() { int i,t; const int G = 3,Gi = invx(G),i2 = invx(2),l = L>>1; t = ksm(G,mod/L), W[0][l] = 1; for(i=1;i<l;i++)W[0][i+l] = (LL)W[0][i+l-1]*t%mod; for(i=l-1;i;i--)W[0][i] = W[0][i<<1]; t = ksm(Gi,mod/L), W[1][l] = 1; for(i=1;i<l;i++)W[1][i+l] = (LL)W[1][i+l-1]*t%mod; for(i=l-1;i;i--)W[1][i] = W[1][i<<1]; for(_i[0]=i=1;i<20;i++)_i[i] = (LL)_i[i-1]*i2%mod; } void NTT(int *f,int len,int sign) { int i = 1,j,k,*w,*p,*q,t; sign = (sign < 0); for(int *r=rev+len+1;i<len;i++,r++)if(i<(k=*r))t=f[i],f[i]=f[k],f[k]=t; for(i=1;i<len;i<<=1)for(j=0;j<len;j+=i<<1)for(w=W[sign]+i,q=(p=f+j)+i,k=0;k<i;k++) {t=*q*LL(*w++)%mod;if((*q=*p-t)<0){*q+=mod;}if((*p+=t)>=mod){*p-=mod;}p++,q++;} if(sign)for(k=_i[__builtin_ctz(len)],i=0;i<len;i++)f[i] = (LL)k*f[i]%mod; } void poly_inv(int *a,int len,int *f) { int l,l2,i;len <<= 1; memset(f,0,len<<2), memset(invtmp,0,len<<2); for(f[0]=invx(a[0]),l=2,l2=4;l<len;l<<=1,l2<<=1) { memcpy(invtmp,a,l<<2), NTT(invtmp,l2,1), NTT(f,l2,1); for(i=0;i<l2;i++)f[i] = f[i]*(2-(LL)invtmp[i]*f[i]%mod+mod)%mod; NTT(f,l2,-1), memset(f+l,0,l<<2); } } void getout(char *s,int d) { int i,l; NTT_Init(); for(i=0;i<d;i++)x[i] = s[i]; for(l=1;l<d;l<<=1); poly_inv(x,l,y); for(i=0;i<d;i++)printf("%d ",y[i]); } } struct complex { double a,b; inline void operator+=(const complex &z){a += z.a, b += z.b;} inline complex operator-(const complex &z){return {a-z.a,b-z.b};} inline complex operator*(const complex &z){return {a*z.a-b*z.b,a*z.b+b*z.a};} }W[2][L]; void FFT_Init() { int i,j; const int l = L>>1; const double pi = acos(-1); for(i=j=1;j<L;j<<=1)for(;i<j<<1;i++)rev[i<<1]=rev[i],rev[i<<1|1]=rev[i]+j; for(i=0;i<l;i++)W[0][i+l] = {cos(pi*i/l),sin(pi*i/l)}; for(i=l-1;i;i--)W[0][i] = W[0][i<<1]; memcpy(W[1],W[0],sizeof W[1]); for(i=1;i<L;i++)W[1][i].b *= -1; } void FFT(complex *f,int len,int sign) { int i = 1,j,k; complex *p,*q,*w,t; sign = (sign < 0); for(int *r=rev+len+1;i<len;i++,r++)if(i<(k=*r))t=f[i],f[i]=f[k],f[k]=t; for(i=1;i<len;i<<=1)for(j=0;j<len;j+=i,j+=i) for(q=(p=f+j)+i,w=W[sign]+i,k=0;k<i;k++,p++,q++)t=*q**w++,*q=*p-t,*p+=t; if(sign){double p=1./len;for(i=0;i<len;i++,f++)f->a *= p, f->b *= p;} } int get(char *s) { int c,l = 0; do c = getchar(); while(c < '1' || c > '9'); do *s++ = char(c^48), l++, c = getchar(); while(c >= '0' && c <= '9'); return l; } char a[L],b[L],bi[L],x[L],y[L]; complex t1[L],t2[L]; LL t3[L+1]; void majutsu(int len) //计算b的倒数,有效位数为len,结果存进bi里 { bool g = 1; int l = 16,l2 = 32,l4 = 64,i; long double d = 0,e = 1; for(i=0;i<20;i++)d = d+e*b[i], e *= 0.1; d = 10./d; if(d < 10) { for(i=0;i<=l;i++)bi[i] = d, d = (d-bi[i])*10; bi[l-1] += (bi[l] > 4); } else bi[0] = 10; while(l < len) {p: memset(t1,0,sizeof(complex)*l4); memset(t2,0,sizeof(complex)*l4); for(i=0;i<l2;i++)t1[i].a = b[i]; for(i=0;i<l;i++)t2[i].a = bi[i]; FFT(t1,l4,1), FFT(t2,l4,1); for(i=0;i<l4;i++) { t1[i] = t1[i]*t2[i]; t1[i].a = 20-t1[i].a, t1[i].b = -t1[i].b; t2[i] = t1[i]*t2[i]; } FFT(t2,l4,-1); t3[l4] = 0; for(i=l4-1;i>=0;i--) { t3[i] = LL(floor(t2[i].a+0.5))+t3[i+1]/10, t3[i+1] %= 10; if(t3[i+1] < 0)t3[i+1] += 10, t3[i]--; } if(t3[0] > 9) { bi[0] = t3[0]/10, t3[0] %= 10; for(i=1;i<l2;i++)bi[i] = t3[i-1]; bi[l2-1] += (t3[l2-1] > 4); } else { for(i=0;i<l2;i++)bi[i] = t3[i]; bi[l2-1] += (t3[l2] > 4); } l <<= 1, l2 <<= 1, l4 <<= 1; } if(g){g = 0, l >>= 1, l2 >>= 1, l4 >>= 1; goto p;} //迭代过程结束后末几位仍会有偏差,于是再单独迭代一次 } int divide(int n,int m) //计算a/b,整数部分存进x里 { int p = n-m+16,l,i; majutsu(p); for(l=1;l<n+p;l<<=1); memset(t1,0,sizeof(complex)*l); memset(t2,0,sizeof(complex)*l); for(i=0;i<n;i++)t1[i].a = a[i]; for(i=0;i<p;i++)t2[i].a = bi[i]; FFT(t1,l,1), FFT(t2,l,1); for(i=0;i<l;i++)t1[i] = t1[i]*t2[i]; FFT(t1,l,-1); t3[l] = 0; for(i=l-1;i>=0;i--)t3[i] = LL(t1[i].a+0.5)+t3[i+1]/10, t3[i+1] %= 10; if(t3[0] > 9) { x[0] = t3[0]/10, t3[0] %= 10, l = n-m+1; for(i=0;i<n-m;i++)x[i+1] = t3[i]; } else for(l=n-m,i=0;i<n-m;i++)x[i] = t3[i]; return l; } void lack(int n,int m,int &d) //微调 { int l,i,j; char t; LL tl = 0; for(i=0,j=n-1;i<j;i++,j--)t = a[i], a[i] = a[j], a[j] = t; for(i=0,j=m-1;i<j;i++,j--)t = b[i], b[i] = b[j], b[j] = t; for(i=0,j=d-1;i<j;i++,j--)t = x[i], x[i] = x[j], x[j] = t; for(l=1;l<n;l<<=1); memset(t1,0,sizeof(complex)*l); memset(t2,0,sizeof(complex)*l); for(i=0;i<m;i++)t1[i].a = b[i]; for(i=0;i<d;i++)t2[i].a = x[i]; t2[0].a += 1.; FFT(t1,l,1), FFT(t2,l,1); for(i=0;i<l;i++)t1[i] = t1[i]*t2[i]; FFT(t1,l,-1); for(i=0;i<=n;i++)tl += LL(t1[i].a+0.5), y[i] = tl%10, tl /= 10; for(i=n;i>=0;i--){if(y[i] > a[i])return;else if(y[i] < a[i])break;} for(x[0]++,i=0;x[i]>9;i++)x[i+1] += x[i]/10, x[i] %= 10; if(x[d])d++; } int main() { int n,m,d; FFT_Init(); n = get(a), m = get(b); d = divide(n,m); lack(n,m,d); NTT::getout(x,d); return 0; } ```