kmjp's blog

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

天下一プログラマーコンテスト2015 予選B : E - 天下一演算

これは自力では思いつかないわ…。
http://tenka1-2015-qualb.contest.atcoder.jp/tasks/tenka1_2015_qualB_e

問題

M未満の非負整数で構成されるN要素の数列Aがある。
この数列に対し、全要素を10倍する処理を何度か行った後、任意の要素を1加算する処理を何度か行った結果、全要素がMの倍数になった。

条件を満たすような、全要素10倍と1加算の処理回数の和の最小値を求めよ。

解法

処理の過程において、各要素のMの剰余を考える。

各要素に10倍を繰り返す場合、10とMが互いに素なら、φ(M)の約数で要素は周期的に変化する。

10とMが互いに素でない場合でも、16回以上10倍すれば、以後φ(M)の約数で要素は周期的に変化する。
Mは10^5以下なので、10倍を16回行うと、その要素(のMの剰余)とMは、素因数分解したときの2と5の指数は一致する(2^17>10^5のため)。
A[i]を10^16した値をBとし、G=GCD(10^16,M)とすると、B/GとM/Gは互いに素なので、その周期はφ(M/G)となるためである。

よって、まず最初に10倍を16回まで行うケースにおける処理回数は愚直に総当たりして求めてしまおう。
あとはA[i]=A[i]*10^16%MでA[i]を置き換えれば、各A[i]は周期的であるとみなせる。

まず1~(M-1)の各値について、A中に何度登場するかを求め、xが登場する回数をB(x)とする。
(A[i]=0の場合、10倍を何回してもそれ以上1加算は不要なので無視してよい)

あるi=1~(M-1)において、C=[i,(i*10^1)%M,(i*10^2)%M, ..., (i*10^(P-1))%M]という数列を考える。(Pは周期、すなわち(i*10^P)%M==iとする)
 \displaystyle f(x)= \sum_{0 \le j \lt P} C( (M-i*10^j\%M)\%M) * (x^j + x^{P+j})という多項式と、 \displaystyle  g(x)= \sum_{0 \le j \lt P} B(i*10^{P-1-j}\%M) * x^jという多項式を考える。
すると f(x)*g(x)における x^{P-1+j}次の項の係数は、j回10倍処理を行ったとき、A[i]のうちCに含まれる要素における10倍処理後の1加算回数に一致する。
よってFFTを用いて f(x)*g(x)を求めることで、10倍処理回数に応じた1加算回数を求めることができる。

Cは1~(M-1)を全てを含むとは限らないので、1~(M-1)を全部1回ずつ含むよう、いくつかのCにおいて上記FFTを行い、それぞれ10倍処理後の1加算回数を求めることで、A[i]全体を10倍処理後における1加算回数を求めることができる。

Mの値によっては、Cが短周期の複数の数列となりうるため、FFTを行う場合は数列数が少ない場合は計算対象の要素数を減らす等、計算量を落とす工夫が必要。

int N,M;
int A[101010];
int num[101010];
int used[101010];
ll tret[201010];
ll ret=1LL<<60;
typedef complex<double> Comp;

vector<Comp> fft(vector<Comp> v, bool rev=false) {
	int n=v.size(),i,j,m;
	
	for(i=0,j=1;j<n-1;j++) {
		for(int k=n>>1;k>(i^=k);k>>=1);
		if(i>j) swap(v[i],v[j]);
	}
	for(int m=2; m<=n; m*=2) {
		double deg=(rev?-1:1) * 2*acos(-1)/m;
		Comp wr(cos(deg),sin(deg));
		for(i=0;i<n;i+=m) {
			Comp w(1,0);
			for(int j1=i,j2=i+m/2;j2<i+m;j1++,j2++) {
				Comp t1=v[j1],t2=w*v[j2];
				v[j1]=t1+t2, v[j2]=t1-t2;
				w*=wr;
			}
		}
	}
	if(rev) FOR(i,n) v[i]*=1.0/n;
	return v;
}

vector<Comp> MultPoly(vector<Comp> P,vector<Comp> Q,bool resize=false) {
	if(resize) {
		int s=2;
		while(s*2<P.size()+Q.size()) s*=2;
		P.resize(s*2);Q.resize(s*2);
	}
	P=fft(P), Q=fft(Q);
	for(int i=0;i<P.size();i++) P[i]*=Q[i];
	return fft(P,true);
}

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>M>>N;
	FOR(i,N) cin>>A[i];
	FOR(i,20) {
		ll tot=i;
		FOR(x,N) tot+=(M-A[x])%M, A[x]=A[x]*10%M;
		ret=min(ret,tot);
	}
	
	int alpha=__gcd(1<<20,M)*__gcd(125*125*125,M);
	if(alpha==M) return _P("%lld\n",ret);
	int per;
	
	FOR(i,N) num[A[i]]++;
	
	vector<vector<ll>> loops;
	for(i=alpha;i<M;i+=alpha) if(used[i]==0) {
		vector<Comp> nu,ad;
		int per;
		x = i;
		FOR(per,M) {
			used[x]=1;
			ad.push_back(M-x);
			nu.push_back(num[x]);
			x=x*10%M;
			if(x==i) break;
		}
		per++;
		reverse(ALL(nu));
		
		FOR(j,per) ad.push_back(ad[j]);
		auto prod = MultPoly(ad,nu,true);
		
		vector<ll> lp;
		FOR(j,per) lp.push_back((ll)(prod[per-1+j].real()+0.1));
		loops.push_back(lp);
	}
	
	int maxper=1;
	FORR(r,loops) maxper=maxper/__gcd(maxper,(int)r.size())*r.size();
	FOR(i,maxper) {
		ll tot=20+i;
		FORR(r,loops) tot += r[i%r.size()];
		ret=min(ret,tot);
	}
	
	cout<<ret<<endl;
	
}

まとめ

よくこんなの本番中に思いつくな…。