kmjp's blog

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

yukicoder : No.434 占い、No.435 占い(Extra)

これまでの過去の★5を考えると、★4でもいい気はするなぁ…
http://yukicoder.me/problems/no/434
http://yukicoder.me/problems/no/435

問題

N桁の整数Sが与えられる。
この整数に対し、隣接する2桁の和を取り、(和が10以上の場合それが1桁になるまで上下桁の和を取ることを繰り返した上で)再び連結して一桁小さな数を作る、という処理を全体が1桁になるまで繰り返す。
最終的に得られる値は何か。

解法

ある数の各桁の総和を問題のように取るということは、元の整数のmod 9を取ったものに等しい。
ただし、S!=0である場合、mod 9を取った結果が0なら解は9とする。(S!=0なら結果が0になり得ないため)。

元のSの各桁が何回分最終的な解に寄与するかを考えると、パスカルの三角形状になっていることがわかる。
すると、求める解は (\sum_{i=0}^{|S|-1} S_i * ({}_{|S|-1} C_i \% 9) ) \% 9 (ただし%9の部分は剰余を取る対象の数が非0で9の倍数の場合、9とする)となる。

ここで問題は合成数で剰余を取るため、良くあるフェルマーの小定理を用いた高速なCombination計算ができないことである。
そこで、あきらめてCombinationにおける分子分母それぞれ素因数分解を行い、それを用いてCombinationを行おう。
その際、最終的に9の剰余を取るので、実際は(素因数%9)を掛けた数を覚えておけば、覚えるべき素因数の数は少なくて済む。

const int prime_max = 10000001;
short divp[prime_max];

void cprime() {
	for(int i=2;i<prime_max;i++) if(divp[i]==0) {
		for(ll j=1LL*i*i;j>i&&j<prime_max;j+=i) divp[j]=i;
	}
}
ll modpow(ll a, ll n, ll mo) {
	ll r=1;
	while(n) r=r*((n%2)?a:1)%mo,a=a*a%mo,n>>=1;
	return r;
}

int T;
int N,X,A,B,M;
int num[12];
void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cprime();
	cin>>T;
	while(T--) {
		cin>>N>>X>>A>>B>>M;
		
		ll ret=0;
		ZERO(num);
		
		for(i=0;i<N;i++) {
			
			if(num[3]<2) {
				x = 1;
				for(j=2;j<=8;j++) x = x*modpow(j,num[j],9);
				ret += x*(X%10);
			}
			else {
				ret += 9*(X%10);
			}
			
			if(i<N-1) {
				x = N-1-i;
				y = 1+i;
				while(divp[x]) num[divp[x]%9]++, x /= divp[x];
				num[x%9]++;
				while(divp[y]) num[divp[y]%9]--, y /= divp[y];
				num[y%9]--;
			}
			
			X = ((X^A)+B)%M;
		}
		while(ret>=10) ret=ret%10+ret/10;
		
		cout<<ret<<endl;
	}
}

解法

数字根っていう言い方、初めて聞いた。