[提高组集训2021] 乘法

一、题目

求 \(n!\) 转成 \(16\) 进制后除去末尾 \(0\) 的最后 \(16\) 位。

\(n<2^{64},T\leq 10\)

二、解法

首先考虑暴力怎么打,我们把所有 \(2\) 的因子提出来之后,剩下的数直接暴力乘法之后自然溢出即可,最后 \(2\) 的因子数模 \(4\) 之后乘上去,转成 \(16\) 进制输出即可。

瓶颈在于把所有数乘起来,设 \(f(n)=\sum_{i=1}^n(2i+1)\),那么答案是 \(\prod_{i=0}^{63}f((\lfloor\frac{n}{2^i}\rfloor-1)/2)\),问题转化为怎么求 \(f(n)\),因为本题是在模 \(2^{64}\) 意义下的,所以选 \(2i\) 这一项的次数是 \(\leq 63\) 的,要不然就被模成 \(0\) 了。

设 \(g(x)\) 表示选了 \(x\) 次 \(2i\) 的 \(i\) 之积,转移考虑加入一个数,但是这个数可能和前面重复所以需要容斥,这里我们容斥最后一个数只出现一次这个条件,然后就足以进入子问题了,我们枚举出现次数 \(j\):

\[g(x)=\sum_{j=1}^x (-1)^{j-1}\cdot g(x-j)\cdot \sum_{i\in[1,n]}i^j \]

最后是一个幂次求和的结构,可以套路地第二类斯特林数反演(直接):

\[\sum_{i=1}^n i^k=\sum_{i=0}^k{k\brace i}\times \frac{1}{i}\times\frac{(n+1)!}{(n-i)!} \]

时间复杂度 \(O(\log^3n)\),至于模意义下的除法可以奇数使用逆元,偶数就一直除 \(2\) 即可。

还有多项式平移倍增的做法,上次省选学过我就不想复读了

三、总结

本题 \(dp\) 加容斥的做法让人耳目一新,两两不同的限制可以被我们拆分成每个数和其他数不同的限制,在我们考虑最后一个数是只用解决它的限制就可以归纳到子问题。

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 1000;
#define int unsigned long long
int T,n,inv[M],S[M][M],tmp[M],g[M];
void init(int n)
{
	//get inv about mod=2^64
	for(int i=1;i<=n;i++)
	{
		inv[i]=1;int nw=i,b=(1ull<<63)-1;
		while(b) {if(b&1)inv[i]*=nw;nw*=nw;b>>=1;}
	}
	//get S(n,m)
	S[0][0]=1;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=i;j++)
			S[i][j]=S[i-1][j-1]+j*S[i-1][j];
}
int div(int x,int y)
{
	while(y%2==0) x/=2,y/=2;
	return x*inv[y];
}
int work(int n)
{
	if(n==0) return 1;
	n=(n-1)/2;
	for(int i=0;i<=63;i++) tmp[i]=g[i]=0;
	for(int i=1;i<=63 && i<=n;i++)
	{
		int A=n+1;
		for(int j=1;j<=i;j++)
		{
			A=A*(n+1-j);
			tmp[i]+=div(A,j+1)*S[i][j];
		}
	}
	g[0]=1;
	for(int i=1;i<=63 && i<=n;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]=div(g[i],i);
	}
	int res=0;
	for(int i=0;i<=63;i++) res+=g[i]<<i;
	return res;
}
signed main()
{
	freopen("multiplication.in","r",stdin);
	freopen("multiplication.out","w",stdout);
	cin>>T;init(64);
	while(T--)
	{
		cin>>n;int c=0,ans=1;
		for(int i=1;i<=63;i++) c+=n>>i;
		for(int i=0;i<=63;i++) ans*=work(n>>i);
		for(int i=1;i<=c%4;i++) ans*=2;
		printf("%llX\n",ans);
	}
}
上一篇:七周打卡剑指offer《剪绳子》


下一篇:el-input中设置onkeypress事件是否匹配正则表达式显示输入内容的格式