题目
题目描述
求
n
!
n!
n! 转成十六进制后,去掉末尾的零的最后
16
16
16 位。
数据范围与提示
n
<
2
64
n<2^{64}
n<264,共
10
10
10 组数据。
思路
显然只需要除掉
2
2
2,就可以去除末尾的零。根据
2
2
2 的次数模
4
4
4 的结果,可以补上后面存在的零,所以问题只在于求出
∏
2
i
+
1
⩽
n
(
2
i
+
1
)
\prod_{2i+1\leqslant n}(2i+1)
2i+1⩽n∏(2i+1)
然后递归
⌊
n
2
⌋
\lfloor{n\over 2}\rfloor
⌊2n⌋ 即可。考虑到
n
n
n 的范围,上面这玩意儿肯定考虑倍增。当知道
∑
T
⫅
S
∣
T
∣
=
i
(
∏
x
∈
T
x
)
\sum_{T\subseteqq S}^{|T|=i}\left(\prod_{x\in T}x\right)
∑T⫅S∣T∣=i(∏x∈Tx) 时,可以很轻易地求出
∑
T
⫅
S
∣
T
∣
=
i
[
∏
x
∈
T
(
x
+
v
)
]
\sum_{T\subseteqq S}^{|T|=i}\left[\prod_{x\in T} (x+v)\right]
∑T⫅S∣T∣=i[∏x∈T(x+v)] 的结果,即枚举
v
v
v 的次数。类似地,我们也可以将两个集合
S
1
,
S
2
S_1,S_2
S1,S2 合并为
S
S
S 的同时维护这个结果。
取模 2 64 2^{64} 264 肯定是突破口。考虑 v = 2 p v=2^p v=2p,那么 v v v 的次数超过 ⌊ 64 p ⌋ \lfloor\frac{64}{p}\rfloor ⌊p64⌋ 时,一切都失去了意义。所以我们只需要存储 ∣ T ∣ ⩾ ∣ S ∣ − 64 |T|\geqslant|S|-64 ∣T∣⩾∣S∣−64 的值,上面两个操作都是 O ( B 2 ) \mathcal O(B^2) O(B2) 的,其中 B = 64 B=64 B=64 。
那么,把 S p = { 1 , 3 , 5 , … , 2 p − 1 } S_p=\{1,3,5,\dots,2^p-1\} Sp={1,3,5,…,2p−1} 的结果预处理出来,然后可以用 S p S_p Sp 的 x x x 加上 2 q ( q < p ) 2^q\;(q<p) 2q(q<p) 再与 S q S_q Sq 并起来,得到 S = { 1 , 3 , 5 , … , 2 p + 2 q − 1 } S=\{1,3,5,\dots,2^{p}+2^{q}-1\} S={1,3,5,…,2p+2q−1} 。所以就用这招,可以 O ( B 3 ) \mathcal O(B^3) O(B3) 的得到任意 n n n 的结果。
再加上外层的 O ( B ) \mathcal O(B) O(B) 递归,复杂度 O ( B 4 ) \mathcal O(B^4) O(B4) 。算出来就感觉过不了,然后它过了……本机测试也跑得非常快……
听了 H a n d I n D e v i l \sf HandInDevil HandInDevil 一席话,发觉这实际上有一个 多项式 的背景。考虑记 F ( x ) = ∏ 1 ⩽ 2 i + 1 ⩽ n ( x + 2 i + 1 ) F(x)=\prod_{1\leqslant 2i+1\leqslant n}(x+2i+1) F(x)=∏1⩽2i+1⩽n(x+2i+1),那么代入任意 2 ∣ x 2\mid x 2∣x 时,都只需计算 x B − 1 x^{B-1} xB−1 以内的几项。(这和 ∣ T ∣ ⩾ ∣ S ∣ − B |T|\geqslant |S|-B ∣T∣⩾∣S∣−B 的含义是相同的。)
而且多项式做上面的操作,可以做到
O
(
B
log
B
)
\mathcal O(B\log B)
O(BlogB),只是没人这么写。并且直接将
x
x
x 代入,就是
O
(
B
)
\mathcal O(B)
O(B) 计算。所以一层的复杂度可以做到
O
(
B
2
)
\mathcal O(B^2)
O(B2),总复杂度
O
(
B
3
)
\mathcal O(B^3)
O(B3) 。
代码
能过的代码为什么要改进呢,给自己搞一堆锅出来?
#include <cstdio>
#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
#include <cctype>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef unsigned long long int_;
inline int readint(){
int a = 0, c = getchar(), f = 1;
for(; !isdigit(c); c=getchar())
if(c == '-') f = -f;
for(; isdigit(c); c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
inline int_ readLong(){
int_ a = 0; int c = getchar();
for(; !isdigit(c); c=getchar());
for(; isdigit(c); c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a;
}
void writeHex(int_ x){
static const char HEX_DIGIT[] = "0123456789ABCDEF";
if(x>>4) writeHex(x>>4);
putchar(HEX_DIGIT[x&15]);
}
const int BASE = 64;
int_ c[BASE][BASE]; // combination
struct Node{
int_ val[BASE]; ///< index: how many UNCHOSEN
/// @brief consider every integers plus ( 1 << @p n )
void shift(int n){
for(int i=1; i<BASE; ++i){
int_ v = 1; // ratio
for(int j=i-1; ~j; --j){
if((v <<= n) == 0) break;
val[j] += c[i][j]*v*val[i];
}
}
}
void operator *= (const Node &t){
for(int i=BASE-1; ~i; --i){
for(int j=1; j+i<BASE; ++j)
val[i+j] += val[i]*t.val[j];
val[i] *= t.val[0];
}
}
};
Node xez[BASE];
int_ solve(int_ n){
bool f = bool(n&1);
Node ans = xez[0]; // empty set
for(int_ x=n-f; x; ){
int d = 63-__builtin_clzll(x);
ans.shift(d); ans *= xez[d];
x ^= (int_(1)<<d); // remove
}
int_ xyx = ans.val[0];
return f ? (xyx*n) : xyx;
}
int main(){
freopen("multiplication.in","r",stdin);
freopen("multiplication.out","w",stdout);
rep(i,0,BASE-1){
c[i][0] = 1; // unsigned long long
rep(j,1,i) c[i][j] = c[i-1][j-1]+c[i-1][j];
}
xez[0].val[0] = 1; // truly empty
xez[1].val[0] = xez[1].val[1] = 1; // when n = 2
rep(i,2,BASE-1){
xez[i] = xez[i-1]; xez[i].shift(i-1);
xez[i] *= xez[i-1];
}
for(int T=readint(); T; --T){
int_ n = readLong(), res = 1;
unsigned char p = 0;
for(; n; n>>=1){
res *= solve(n);
p = char((p+(n>>1))&3);
}
writeHex(res<<p), putchar('\n');
}
return 0;
}