ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

[提高组集训2021] 乘法

2021-10-30 17:00:57  阅读:169  来源: 互联网

标签:frac 16 int sum 集训 64 2021 2i 乘法


一、题目

求 \(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);
	}
}

标签:frac,16,int,sum,集训,64,2021,2i,乘法
来源: https://www.cnblogs.com/C202044zxy/p/15485478.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有