【题解】P8350 [SDOI/SXOI2022] 进制转换

来描述一下 EI 做法。如有意会错误之处,望海涵。

不妨将题面中的 \(1\le i\le n\) 改为 \(0\le i\le n\)

为描述方便,提前声明最终时间复杂度为 \(O(\sqrt n)\)

并设二阈值 \(B_1=2^{c_1},B_2=3^{c_2}\) 使 \(c_1,c_2\in \mathbf{N}^*,B_1=\Theta(\sqrt n),B_2=\Theta(\sqrt n)\)

对于原题面中的 \(i\),分别枚举其在两个进制下的低位、高位,将所求转写为如下和式:

\[\sum_{i_1+B_1j_1=i_2+B_2j_2}a_1(i_1)a_2(j_1)b_1(i_2)b_2(j_2) \]

其中 \(0\le i_1<B_1,0\le i_2<B_2\) 为所枚举低位。

\(a_1(i)=a_2(i)=y^{a(i)},b_1(i)=x^iz^{b(i)},b_2(i)=x^{iB_2}z^{b(i)}\)

为使求和不超过题面所述上界 \(n\),可考虑对 \(j_1\) 作限制 \(0\le j_1<\left\lfloor\frac{n}{B_1}\right\rfloor\)。此时 \(i_1+B_1j_1\) 会恰取遍 \([0,\left\lfloor\frac{n}{B_1}\right\rfloor B_1)\) 中的每一个数。因此若在此条件下计算出上述和式,则我们只需枚举剩余不超过 \(B_1\) 项即可得到最终答案。

计算和式的关键在于将和式中的限制移项为:

\[i_1-i_2=B_2j_2-B_1j_1 \]

我们可以考虑分别计算

\[f_n=\sum_{i_1-i_2=n}a_1(i_1)b_1(i_2) \]

\[g_n=\sum_{B_2j_2-B_1j_1=n}a_2(j_1)b_2(j_2) \]

\(-B_2<i_1-i_2<B_1\)。因此分别计算两数列下标在此区间内的值即可。

先考虑算 \(g\)。先枚举 \(j_2\),则由于 \(B_1,B_2\) 同阶,使 \(B_2j_2-B_1j_1\) 落在此区间内的 \(j_1\) 仅有 \(O(1)\) 个,因此暴力枚举即可做到 \(O(\sqrt n)\) 的复杂度。

至于算 \(f\),可以考虑逐位倍增计算:设我们当前计算出的 \(f\) 是在 \(0\le i_1<2^{d_1},0\le i_2<3^{d_2}\) 的限制下,欲将之倍增至 \(0\le i_1<2^{d_1+1}\)。由于函数 \(a_1\) 具有良好的按位独立性质,可以得到如下递推式:

\[f'_n=f_n+a_1(2^{d_1})\times f_{n-2^{d_1}} \]

\(3^{d_2}\) 倍增至 \(3^{d_2+1}\) 方法类似。每次取 \(2^{d_1}\)\(3^{d_2}\) 之较小者倍增,可保持 \(2^{d_1}\)\(3^{d_2}\) 同阶,而使总复杂度做到 \(O(\sqrt n)\)

注意在计算 \(a_1\) 等函数时,不要由于快速幂或求各数位之和而使复杂度多带 \(\log\),具体实现可见代码。由于算法常数很小,目前是最优解。

#include<cstdio>
#include<numeric>
#include<cassert>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cstdlib>
using namespace std;

int read();
typedef long long ll;
#define fr(i,l,r) for(int i=(l);i<=(r);++i)
#define rf(i,l,r) for(int i=(l);i>=(r);--i)
#define fo(i,l,r) for(int i=(l);i<(r);++i)
#define foredge(i,u,v) for(int i=fir[u],v;v=to[i],i;i=nxt[i])
#define filein(File) freopen(File".in","r",stdin)
#define fileout(File) freopen(File".out","w",stdout)

const int N1=(1<<22)+5,N2=4782969+5,MOD=998244353;
ll qpow(ll a,int x) {
	ll z=1;
	for(;x;x>>=1,a=a*a%MOD)
		if(x&1) z=z*a%MOD;
	return z;
}
ll n,lim1,lim2,B1,B2;
int x,y,z,s1,s2,c1,c2;
ll f[N1+N2],g[N1+N2],tf[N1+N2];
void calcf() {
	ll len1=1,len2=1;
	f[N2]=1;
	while(len1<B1||len2<B2) if(len1<len2) {
		rf(i,len1-1,-len2+1) (f[i+len1+N2]+=f[i+N2]*y)%=MOD;
		len1*=2;
	} else {
		ll val=qpow(x,len2)*z%MOD;
		fr(i,-len2+1,len1-1) {
			ll tmp=f[i+N2]*val%MOD;
			(f[i-len2+N2]+=tmp)%=MOD;
			(f[i-len2*2+N2]+=tmp*val)%=MOD;
		}
		len2*=3;
	}
}
int d2[50],d3[30],cnt2,cnt3;
void init(ll x) {
	cnt2=cnt3=0; ll i;
	i=x; fo(j,0,44) cnt2+=d2[j]=i%2,i/=2;
	i=x; fo(j,0,28) cnt3+=d3[j]=i%3,i/=3;
}
void inc() {
	++d2[0]; ++d3[0]; ++cnt2; ++cnt3;
	fo(j,0,44) if(d2[j]>=2) d2[j]=0,++d2[j+1],cnt2-=1;
	else break;
	fo(j,0,28) if(d3[j]>=3) d3[j]=0,++d3[j+1],cnt3-=2;
	else break;
}
static ll pw2[50],pw3[60];
void calcg() {
	init(0);
	ll xB2=qpow(x,B2),xi=1;
	for(ll i=0;i<lim2;++i) {
		for(ll j=i*B2/B1;j<lim1&&i*B2-j*B1>-B2;++j)
			(g[i*B2-j*B1+N2]+=xi*pw3[cnt3]%MOD*pw2[__builtin_popcountll(j)])%=MOD;
		xi=xi*xB2%MOD;
		inc();
	}
}


int main() {
	cin>>n>>x>>y>>z;
	*pw2=1; fr(i,1,45) pw2[i]=pw2[i-1]*y%MOD;
	*pw3=1; fr(i,1,56) pw3[i]=pw3[i-1]*z%MOD;
	for(ll p1=1;p1<n;p1*=2) ++s1;
	for(ll p2=1;p2<n;p2*=3) ++s2;
	c1=s1+1>>1,c2=s2+1>>1;
	B1=1; fr(i,1,c1) B1*=2;
	B2=1; fr(i,1,c2) B2*=3;

	lim1=n/B1; lim2=n/B2;
	calcf(); calcg();
	ll ans=0;
	fr(i,-B2+1,B1-1) (ans+=f[i+N2]*g[i+N2])%=MOD;
	ll lim=min(lim1*B1,lim2*B2);
	init(lim);
	ll pw=qpow(x,lim%(MOD-1));
	for(ll i=lim;i<=n;++i) {
		(ans+=pw*pw2[cnt2]%MOD*pw3[cnt3])%=MOD;
		pw=pw*x%MOD; inc();
	}
	printf("%lld\n",(ans-1+MOD)%MOD);
	return 0;
}

const int S=1<<21;
char p0[S],*p1,*p2;
#define getchar() (p2==p1&&(p2=(p1=p0)+fread(p0,1,S,stdin))==p1?EOF:*p1++)
inline int read() {
	static int x,c,f;x=0;f=1;
	do c=getchar(),c=='-'&&(f=-1); while(!isdigit(c));
	do x=x*10+(c&15),c=getchar(); while(isdigit(c));
	return x*f;
}
posted @ 2022-06-28 19:52  秋叶冬雪  阅读(100)  评论(0编辑  收藏  举报