[海军国际项目办公室]乘法
乘法
题目概述


题解
显然,我们去掉末尾
0
0
0保留的最后
16
16
16位,转化成二进制位后末尾的
0
0
0不可能超过
3
3
3个,我们可以将原来
n
!
n!
n!中的
0
0
0全部去掉,最后在求出
0
0
0的个数,模
4
4
4后乘上。
既然,我们已经去掉所有的
2
2
2的倍数了,那么我们乘的肯定都是奇数,我们可以按照原数中含
2
2
2的个数分类,定义
f
n
=
∏
i
∈
[
1
,
n
]
,
2
∤
i
i
f_{n}=\prod_{i\in[1,n],2 \nmid i}i
fn=∏i∈[1,n],2∤ii,显然有
A
n
s
=
∏
i
=
0
log
n
f
n
2
i
Ans=\prod_{i=0}^{\log\,n}f_{\frac{n}{2^i}}
Ans=i=0∏lognf2in
我们考虑
f
n
f_n
fn怎么算,显然我们的所有奇数都是可以用
2
k
+
1
(
k
∈
N
)
2k+1(k\in N)
2k+1(k∈N)的形式表示的,那么我们
f
f
f的式子可以写成,
f
n
=
∏
i
=
0
⌊
n
−
1
2
⌋
(
2
i
+
1
)
f_{n}=\prod_{i=0}^{\lfloor\frac{n-1}{2}\rfloor}(2i+1)
fn=i=0∏⌊2n−1⌋(2i+1)
由于我们只会保留前面的
64
64
64位,我们可以将它看成一个多项式,令
x
=
2
x=2
x=2,取模
x
64
x^{64}
x64。
令
F
F
F为该多项式,有,
F
(
x
)
=
∏
i
=
0
⌊
n
−
1
2
⌋
(
i
x
+
1
)
(
m
o
d
x
64
)
f
=
∑
i
=
0
63
2
i
F
[
x
i
]
F(x)=\prod_{i=0}^{ \lfloor\frac{n-1}{2}\rfloor}(ix+1)\,\,(\mod x^{64})\\ f=\sum_{i=0}^{63}2^iF[x^i]
F(x)=i=0∏⌊2n−1⌋(ix+1)(modx64)f=i=0∑632iF[xi]
我们不妨记
u
p
=
⌊
n
−
1
2
⌋
up=\lfloor\frac{n-1}{2}\rfloor
up=⌊2n−1⌋,记
g
(
u
p
,
i
)
=
(
∏
i
=
0
u
p
(
i
x
+
1
)
)
[
x
i
]
g(up,i)=(\prod_{i=0}^{up}(ix+1))[x^i]
g(up,i)=(∏i=0up(ix+1))[xi]。
考虑如何快速地求出我们的
g
(
u
p
,
i
)
[
i
∈
[
0
,
63
]
]
g(up,i)[i\in[0,63]]
g(up,i)[i∈[0,63]]
考虑我们上面多项式系数的组合意义,
F
[
x
i
]
F[x^i]
F[xi]相当于在
[
1
,
u
p
]
[1,up]
[1,up]中选出
i
i
i个数,将每个方案中选出的数的积求和。
没有重复元素的无序序列的乘积,这就显然是可以背包求出的了。
我们考虑在前面的无序序列的最后加一个数,这数可能与前面的数重复,就需要容斥,枚举重复的元素个数,
g
(
x
,
i
)
=
1
i
∑
j
=
1
i
(
−
1
)
j
−
1
g
(
x
,
i
−
j
)
∑
y
∈
[
1
,
x
]
y
j
g(x,i)=\frac{1}{i}\sum_{j=1}^{i}(-1)^{j-1}g(x,i-j)\sum_{y\in[1,x]}y^j
g(x,i)=i1j=1∑i(−1)j−1g(x,i−j)y∈[1,x]∑yj
该重复的数在
[
1
,
x
]
[1,x]
[1,x]中是什么都有可能,因为序列求的是乘积,所以我们肯定是将重复的数的幂与
g
(
x
,
i
−
j
)
g(x,i-j)
g(x,i−j)相乘,得到新的序列的乘积。
而不同的序列之间求的是加减,所以是幂和。
关于
∑
y
∈
[
1
,
x
]
y
j
\sum_{y\in[1,x]}y^j
∑y∈[1,x]yj的求法,我们在微信步数中讲到过的,可以通过斯特林数反演求出,所以有,
g
(
x
,
i
)
=
1
i
∑
j
=
1
i
(
−
1
)
j
−
1
g
(
x
,
i
−
j
)
∑
k
=
1
j
k
!
{
j
k
}
(
x
+
1
k
+
1
)
g(x,i)=\frac{1}{i}\sum_{j=1}^{i}(-1)^{j-1}g(x,i-j)\sum_{k=1}^{j}k!{j\brace k}\binom{x+1}{k+1}
g(x,i)=i1j=1∑i(−1)j−1g(x,i−j)k=1∑jk!{kj}(k+1x+1)
后面的
∑
k
=
1
j
k
!
{
j
k
}
(
x
+
1
k
+
1
)
\sum_{k=1}^{j}k!{j\brace k}\binom{x+1}{k+1}
∑k=1jk!{kj}(k+1x+1),由于只与
x
,
j
x,j
x,j相关,我们可以预处理出来,预处理大概是
O
(
log
2
n
o
r
log
3
n
)
O\left(\log^2n\,or\,\log^3n \right)
O(log2norlog3n)。
而我们前面的
1
i
\frac{1}{i}
i1是不大好处理的,我们的模数是
2
64
2^{64}
264次方,我们在这个范围下是不大好求出逆元的。
很显然,对于奇数,一定是与
2
64
2^{64}
264次方互质的,我们可以直接通过欧拉定理求出它的逆元,
φ
(
2
64
)
=
2
63
\varphi(2^{64})=2^{63}
φ(264)=263,直接求
k
2
63
−
1
m
o
d
2
64
k^{2^{63}-1}\mod 2^{64}
k263−1mod264。
而对于偶数,由于我们答案是整数,所以除之前的数也一定是偶数,我们可以先将
2
2
2的幂除掉,知道我们的除数变为一个奇数,再乘它的逆元。
C
202044
z
x
y
\color{black}{C}\color{red}{202044zxy}
C202044zxy提出在除
2
2
2的幂的同时,由于我们之前取模的是
2
64
2^{64}
264次方,可能会导致超出
2
64
2^{64}
264的部分一回来的时候丢失。
对于
g
(
x
,
i
)
g(x,i)
g(x,i)由于我们除的是
i
i
i,算到
f
n
f_n
fn中的时候,会乘上一个
2
i
2^i
2i,
2
i
2^i
2i中
2
2
2的幂次肯定是大于
i
i
i中
2
2
2的幂次,所以丢失的部分在加入答案时也是一定会被模掉的。
但我们在求组合数时这部分是不可忽略的,这部分不一定能被抵消的(有可能也可以抵消,不过我没去算),我们有两种方法来维护。
一种是直接维护阶乘的序列,显然
(
n
m
)
=
∏
i
=
1
n
(
n
−
i
+
1
)
∏
i
=
1
m
i
\binom{n}{m}=\frac{\prod_{i=1}^{n}(n-i+1)}{\prod_{i=1}^{m}i}
(mn)=∏i=1mi∏i=1n(n−i+1),我们可以暴力维护这两项产生的乘的序列,将下面的数与上面它的倍数相匹配,除掉后再乘起来得到组合数,这样的话预处理是
O
(
log
3
n
)
O\left(\log^3n\right)
O(log3n)的。
而另一种方法是直接暴力用 __int128 保留
128
128
128位,这样我们往回依旧不会产生冲突了 ,它总不会要移动到128位后吧,这样就是
O
(
log
2
n
)
O\left(\log^2n\right)
O(log2n)的。
或者说你也可以猜测一下前面的
2
i
2^i
2i的
2
2
2的幂次很多,应该也可以让后面丢失的部分没有影响。
当然,求
g
g
g的部分也是
O
(
log
2
n
)
O\left(\log^2n\right)
O(log2n)的。
前面预处理也是
O
(
log
2
n
)
O\left(\log^2n\right)
O(log2n)的。
我们总共要求
log
n
\log\,n
logn个
f
f
f,所以是
O
(
log
3
n
)
O\left(\log^3n\right)
O(log3n)的。
总时间复杂度
O
(
T
log
3
n
)
O\left(T\log^3n\right)
O(Tlog3n)。
源码
我这的代码是猜测不会影响的,也可以过。
PS.可以用占位符
%
l
l
X
\%llX
%llX输出
16
16
16进制,我考试的时候手动转
16
16
16进制多输出了一个不可见字符,然后就爆零了。
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) (x&-x)
#define reg register
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
typedef long long LL;
typedef unsigned long long uLL;
const int INF=0x3f3f3f3f;
const int mo=1e9+7;
const int inv2=499122177;
const double jzm=0.997;
const int zero=10000;
const int orG=3,invG=332748118;
const double Pi=acos(-1.0);
const double eps=1e-5;
typedef pair<LL,int> pii;
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
_T f=1;x=0;char s=getchar();
while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
x*=f;
}
template<typename _T>
void print(_T x){if(x<0){x=(~x)+1;putchar('-');}if(x>9)print(x/10);putchar(x%10+'0');}
uLL gcd(uLL a,uLL b){return !b?a:gcd(b,a%b);}
int add(int x,int y,int p){return x+y<p?x+y:x+y-p;}
void Add(int &x,int y,int p){x=add(x,y,p);}
int qkpow(int a,int s,int p){int t=1;while(s){if(s&1)t=1ll*a*t%p;a=1ll*a*a%p;s>>=1;}return t;}
int T;uLL n,f[70],S[70][70],ans,fac[70],tmp[70],g[70],ff[70],gg[70];
char answer[70];
void init(){
S[0][0]=fac[0]=1;
for(int i=1;i<=64;i++)
for(int j=1;j<=64;j++)
S[i][j]=S[i-1][j-1]+1ull*j*S[i-1][j];
for(int i=1;i<=64;i++)fac[i]=1ull*i*fac[i-1];
}
uLL expt(uLL x,int d){return (x>>gg[d])*ff[d];}
uLL sakura(uLL x){
if(!x)return (uLL)1;uLL up=(x-1uLL)>>1uLL,res=0;
for(int i=0;i<=63&&i<=up;i++)g[i]=0;
for(int i=1;i<=63&&i<=up;i++){
tmp[i]=0;LL pp=up+1uLL;
for(int j=1;j<=i;j++){
pp*=up+1uLL-1ull*j;uLL t=1ull*j+1uLL;
pp=expt(pp,j+1);tmp[i]+=pp*fac[j]*S[i][j];
}
}
g[0]=1;
for(int i=1;i<=63&&i<=up;i++){
for(int j=1;j<=i;j++)
if(j&1)g[i]+=g[i-j]*tmp[j];
else g[i]-=g[i-j]*tmp[j];
g[i]=expt(g[i],i);
}
for(int i=0;i<=63&&i<=up;i++)res+=(g[i]<<i);
return res;
}
char Turn(uLL x){
if(x<10)return x+'0';
return x-10+'A';
}
signed main(){
freopen("multiplication.in","r",stdin);
freopen("multiplication.out","w",stdout);
read(T);init();
for(int i=1;i<=64;i++){
gg[i]=0;ff[i]=1;uLL s=(1uLL<<63)-1uLL,a=i;
while(!(a&1uLL))gg[i]++,a>>=1;
if(gg[i])ff[i]=ff[i>>gg[i]];
else while(s){if(s&1uLL)ff[i]=a*ff[i];a=a*a;s>>=1uLL;}
}
while(T--){
read(n);ans=1;uLL sum=0;
for(int i=0;i<64;i++)ans*=sakura(n>>i);
for(int i=1;i<64;i++)sum+=(n>>i);
sum&=3uLL;ans<<=sum;
printf("%llX\n",ans);
}
return 0;
}

浙公网安备 33010602011771号