题解 乘法

传送门

好题!

先考虑如何去掉末位的0
在十六进制下末位有0等价于这个数是16的倍数
直接提出所有16不太好写,所以可以先提出所有的2
最终将2的个数模4乘回去即可

于是现在要算 \(n!\) 提出所有2的结果
可以奇偶分治,先算出 \([1, n]\) 中所有奇数的积,从所有偶数中提出一个2,再递归算这些偶数都除掉一个2的乘积
2的个数很好统计,每一层就是 \(\lfloor \frac{n}{2} \rfloor\)
然后考虑怎么算 \([1, n]\) 中奇数之积
即为 \(\prod\limits_{i=1}^{\frac{n-1}{2}}(2i+1)\)
考虑如何在 \(\pmod{2^{64}}\) 的意义下处理这个式子
将括号直接拆开,发现如果有一项的项数 \(\geqslant64\) 则一定不产生贡献
于是们可以枚举项数,将其中2的次幂提出来
则问题转化为「在 \([1, n]\) 的数中任选 \(m\) 个数的所有方案中的 \(m\) 个数之积」
考虑这个东西怎么算
若令 \(g_{i, j}\) 为从 \(i\) 个数中任选 \(j\) 个数的所有方案的乘积之和
有转移

\[g_{i, j}=g_{i-1, j}+i*g_{i-1, j-1} \]

这个东西直接算是 \(O(\text{炸天})\) 的,所以可以插值求,但是我太菜了不会插值
于是有dalao的一种处理方法转化出了斯特林数
但还有另一种理解方式:
我们发现现在是要从 \([1, n]\)\(n\) 个数中选 \(m\) 个产生贡献,剩下的贡献为1
写出来就是 \(\sum_i\prod_{j=1}^n x_j\),这里

\[x_j= \begin{cases} j&\text{j被选中}\\ 1&\text{j不被选中}\end{cases} \]

就是说 \(j\) 的贡献是 \(j\) 或 1,而要求的是有恰好 \(m\) 个数产生贡献的方案的贡献和
试着构造一下递推式
发现方案数很好算,是 \(f_{i, j}=f_{i-1, j}+f_{i-1, j-1}\)
试着让它能一并计算贡献
这里一共有两类转移,发现第二类转移恰好会做 \(m\)
我们需要在转移中乘上数次当前这个数
发现与 \(\begin{bmatrix}i\\ j\end{bmatrix}=\begin{bmatrix}i-1\\ j-1\end{bmatrix}+(i-1)\begin{bmatrix}i-1\\ j\end{bmatrix}\) 类似
发现在 \(\begin{bmatrix}0\\ 0\end{bmatrix}\)\(f\begin{bmatrix}n\\ k\end{bmatrix}\) 的所有转移路径上恰好会有 \(n-k\) 个数乘上它本身
我们想让它乘的是它自身-1,于是我们做到 \(n+1\)
于是我们想求的答案就是所有转移路径的和,即为 \(\begin{bmatrix}n+1\\ n-m+1\end{bmatrix}\)

  • 于是形如 \(n\) 个数中选 \(m\) 个求和之类都能类似地构造?那第二类斯特林数的递推式能拿来干嘛

于是式子有了,现在想在log级别求出一个斯特林数
不太可求,但发现 \(n, m\) 的差值很小,于是可以暴力DP
\(dp[i][j]\)\(i\) 个数组成 \(j\)\(\geqslant 2\) 的环的方案数
于是 \(dp[i][j] = \sum\limits_{k=2}^{i}\binom{i-1}{k-1}dp[i-k][j-1]\begin{bmatrix}k\\ 1\end{bmatrix}\)
于是可以DP,统计答案就是枚举状态乘上 \(\binom{n}{i}\)
这里组合数的话可以提出所有偶数,奇数可以欧拉定理预处理逆元
复杂度 \(O(Tlog^4n)\) 但跑不满

Code:
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 100010
#define ll long long
#define ull unsigned long long
#define int long long

ull n, T; ll m=63;
ull dp[145][145], fac[145], C[145][145], inv[145], ans, cnt;
ull s[1000][1000];
inline ull qpow(ull a, ull b) {ull ans=1; for (; b; a=a*a,b>>=1) if (b&1) ans=ans*a; return ans;}
ull count(ull n) {ull ans=0; while (!(n&1)) ++ans, n>>=1; return ans;}
ull del(ull n) {while (!(n&1)) n>>=1; return n;}
ull C2(ull n, ull k) {
	// return !k?1:C[n][k];
	// cout<<"C: "<<n<<' '<<k<<endl;
	if (!k) return 1;
	if (n<k) return 0;
	ull cnt=0, ans=1;
	for (ull i=0; i<k; ++i) {
		ans*=del(n-i);
		cnt+=count(n-i);
	}
	for (ull i=1; i<=k; ++i) {
		ans*=inv[del(i)];
		cnt-=count(i);
	}
	return ans*qpow(2, cnt);
}
ull prod(ull n) {
	// cout<<"in_prod: "<<n<<endl;
	ull ans=0;
	#if 0
	for (int i=0; i<64; ++i) if (n+1>=i) ans+=(1llu<<i)*s[n+1][n-i+1]; //, cout<<"add: "<<(1llu<<i)*s[n+1][n-i+1]<<endl;
	return ans;
	#else
	for (int m=0; m<64; ++m) if (n+1>=m) {
		ull tem=0;
		for (int i=m; i<=140; ++i) if (dp[i][i-m]) tem+=C2(n+1, i)*dp[i][i-m]; //, cout<<"i: "<<i<<' '<<C2(n+1, i)<<' '<<dp[i][i-m]<<endl;
		ans+=(1llu<<m)*tem;
		// cout<<"add: "<<(1llu<<m)*tem<<endl;
	}
	// cout<<"return: "<<ans<<endl;
	return ans;
	#endif
}
ull calc(ull n) {
	// cout<<"calc: "<<n<<endl;
	if (!n) return 1;
	ull ans=prod((n-1)/2);
	// cout<<"prod: "<<(n-1)/2<<' '<<prod((n-1)/2)<<endl;
	cnt=(cnt+n/2)%4;
	return ans*calc(n/2);
}

signed main()
{
	freopen("multiplication.in", "r", stdin);
	freopen("multiplication.out", "w", stdout);

	s[0][0]=1;
	for (int i=1; i<1000; ++i) for (int j=1; j<=i; ++j) s[i][j]=s[i-1][j-1]+(i-1)*s[i-1][j];
	for (int i=0; i<=140; ++i) for (int j=C[i][0]=1; j<=i; ++j) C[i][j]=C[i-1][j-1]+C[i-1][j];
	fac[0]=fac[1]=1;
	for (int i=2; i<=140; ++i) fac[i]=fac[i-1]*i;
	for (int i=1; i<=140; ++i) if (i&1) inv[i]=qpow(i, (1llu<<63)-1);
	dp[0][0]=1;
	for (int i=1; i<=140; ++i) {
		for (int j=1; j<=i; ++j) {
			for (int k=2; k<=i; ++k) {
				dp[i][j]+=C[i-1][k-1]*dp[i-k][j-1]*fac[k-1];
			}
			// if (i<=5 && j<=5) printf("dp[%d][%d]=%llu\n", i, j, dp[i][j]);
		}
	}
	
	cin>>T;
	while (T--) {
		cin>>n;
		cnt=0;
		ans=calc(n);
		// cout<<"ans: "<<ans<<endl;
		// cout<<"cnt: "<<cnt<<endl;
		printf("%llX\n", ans*qpow(2, cnt));
		// cout<<ans*qpow(2, cnt)<<endl;
	}
	

	return 0;
}
posted @ 2021-10-31 10:12  Administrator-09  阅读(8)  评论(0编辑  收藏  举报