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

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

不妨将题面中的 1in 改为 0in

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

并设二阈值 B1=2c1,B2=3c2 使 c1,c2N,B1=Θ(n),B2=Θ(n)

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

i1+B1j1=i2+B2j2a1(i1)a2(j1)b1(i2)b2(j2)

其中 0i1<B1,0i2<B2 为所枚举低位。

a1(i)=a2(i)=ya(i),b1(i)=xizb(i),b2(i)=xiB2zb(i)

为使求和不超过题面所述上界 n,可考虑对 j1 作限制 0j1<nB1。此时 i1+B1j1 会恰取遍 [0,nB1B1) 中的每一个数。因此若在此条件下计算出上述和式,则我们只需枚举剩余不超过 B1 项即可得到最终答案。

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

i1i2=B2j2B1j1

我们可以考虑分别计算

fn=i1i2=na1(i1)b1(i2)

gn=B2j2B1j1=na2(j1)b2(j2)

B2<i1i2<B1。因此分别计算两数列下标在此区间内的值即可。

先考虑算 g。先枚举 j2,则由于 B1,B2 同阶,使 B2j2B1j1 落在此区间内的 j1 仅有 O(1) 个,因此暴力枚举即可做到 O(n) 的复杂度。

至于算 f,可以考虑逐位倍增计算:设我们当前计算出的 f 是在 0i1<2d1,0i2<3d2 的限制下,欲将之倍增至 0i1<2d1+1。由于函数 a1 具有良好的按位独立性质,可以得到如下递推式:

fn=fn+a1(2d1)×fn2d1

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

注意在计算 a1 等函数时,不要由于快速幂或求各数位之和而使复杂度多带 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 @   秋叶冬雪  阅读(133)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示