kmjp's blog

競技プログラミング参加記です

yukicoder : No.2703 FizzBuzz Letter Counting

解法を思いついても、短く実装するのつらそう。
https://yukicoder.me/problems/no/2703

問題

最大10^17桁の整数Nが、RLE形式で与えられる。
1~NについてFizzBuzz問題を解いた時、出力されるアルファベット及び数字は何個か。
998244353で剰余を取った値を求めよ。

解法

2桁以上でd桁の数は15の倍数個ある。
FizzBuzzは連続する15整数毎にパターンを繰り返すことを考える。

Nの桁数をLとする。
まず(L-1)桁以下の整数について考える。
d桁の数は9*10^(d-1)個あり、その桁の数におけるFizzBuzz問題の出力数は(48d+192)*10^(d-2)である。
d=1の場合は愚直に解くとして、d=2~(L-1)の場合のこれらの総和は、行列累乗で求められる。

次にL桁の整数について考える。
このような整数はN+1-(10^(L-1))個あり、15整数毎のループがfloor*1 % (998244353*15)を求め、上記floorの値と、15で割った余りを求めよう。

int N;
ll V[101010],L[101010];
const ll mo=998244353LL*15;

const int MAT=4;
struct Mat { ll v[MAT][MAT]; Mat(){ZERO(v);};};

Mat mulmat(Mat& a,Mat& b,int n=MAT) {
	int x,y,z; Mat r;
	FOR(x,n) FOR(y,n) r.v[x][y]=0;
	FOR(x,n) FOR(z,n) FOR(y,n) {
		r.v[x][y] = (r.v[x][y]+(__int128)a.v[x][z]*b.v[z][y])%mo;
	}
	return r;
}

Mat powmat(ll p,Mat a,int n=MAT) {
	int i,x,y; Mat r;
	FOR(x,n) FOR(y,n) r.v[x][y]=0;
	FOR(i,n) r.v[i][i]=1;
	while(p) {
		if(p%2) r=mulmat(r,a,n);
		a=mulmat(a,a,n);
		p>>=1;
	}
	return r;
}
__int128 modpow(__int128 a, __int128 n = mo-2) {
	__int128 r=1;a%=mo;
	while(n) r=r*((n%2)?a:1)%mo,a=a*a%mo,n>>=1;
	return r;
}

// 長さlenのrepunit数と、10^len
template<typename V> pair<V,V> repunit(ll len) {
	if(len==1) return {1,10};
	auto a=repunit<V>(len/2);
	a.first=(a.first*a.second+a.first)%mo;
	a.second=a.second*a.second%mo;
	if(len%2) {
		a.first=(a.first*10+1)%mo;
		a.second=(a.second*10)%mo;
	}
	return a;
}


void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>N;
	ll SL=0;
	__int128 num=0;
	FOR(i,N) {
		cin>>V[i]>>L[i];
		SL+=L[i];
		num=(num*modpow(10,L[i])+V[i]*repunit<__int128>(L[i]).first)%mo;
	}
	
	if(SL<=6) {
		int ret=0;
		for(i=1;i<=num;i++) {
			if(i%3==0) ret+=4;
			if(i%5==0) ret+=4;
			if(i%3&&i%5) {
				x=i;
				while(x) ret++, x/=10;
			}
		}
		cout<<ret<<endl;
		return;
	}
	
	Mat A;
	A.v[0][0]=10;
	A.v[0][1]=10;
	A.v[1][1]=10;
	A.v[2][0]=1;
	A.v[2][1]=4;
	A.v[2][2]=1;
	A=powmat(SL-2,A);
	ll ret=(A.v[2][0]*96+A.v[2][1]*48+21)%mo;
	
	
	
	num+=mo-modpow(10,SL-1)+1;
	num%=mo;
	ret+=(num/15)*((8*SL+32)%mo)%mo;
	for(i=100;i<100+(num%15);i++) {
		if(i%3==0) ret+=4;
		if(i%5==0) ret+=4;
		if(i%3&&i%5) ret+=SL%mo;
	}
	
	cout<<ret%998244353<<endl;
	
}

まとめ

等比数列の和からどうにかしようと思ったけど、行列累乗の方が楽だな。

*1:N+1-(10^(L-1)))/15)個ある。 またループ毎に(8L+32)文字が出力される。 そこで、N+1-(10^(L-1