AOJ1310 Find the Multiples

問題リンク Find the Multiples

  • 概要

a_0 〜 a_(n-1)の[0,9]の列と素数Qが与えられる。i <= jでかつa_i≠0であるような部分列
a_i a_(i+1) ... a_j
を10進数の正の整数とみなす。リーディング0なものは認めない。この正の整数がQの倍数であるような(i, j)の組がいくつあるか答えよ。答えは2^30を超えない。
a_0 ... a_(n-1)の列は問題文にあるプログラム片で求めること。
1 <= N <= 10^5
Q < 10^8 && prime

  • 解法

(i, j)の部分列a_i...a_jは
a_i a_(i+1) ... a_(n-1) - a_(j+1) a_(j+2) ... a_(n-1)
という引き算と同じことになります。ここで、もし
{ a_i a_(i+1) ... a_(n-1) } mod Q - { a_(j+1) a_(j+2) ... a_(n-1) } mod Q
が0になったとき、つまり、両式のmod Qが同じだったとき且つこのときに限り、部分列a_i ... a_jはQの倍数になります( X = A - B ⇒ X mod Q = A mod Q - B mod Q で右辺が0になっているから)。なので、A_i = a_i ... a_(n-1) mod Qを各iについて求め、i < jでA_i mod Q = A_j mod Q が等しいものがいくつあるかをカウントすれば解けます。A_iをA_(i+1)から求めるために
A_i = ( a[i] * 10^k + A_(i+1) ) mod Q (kは適切な値)
として計算しました。10^kの部分は適宜mod Qを取っていけばオーバーフロー地獄になることはありません。
10^k のところでmod Qを使うので、Qが2や5だとうまく計算できません。ですが、Qが2や5の場合、部分列の一番下の数字だけで、Qの倍数が判定できるので心配ありません。

追記
すっごい怪しい議論をしていることに気付いた。
X = a_i ... a_j
A = a_i ... a_(n-1)
B = a_(j+1) ... a_(n-1)
A - Bの引き算って
a_i ... a_j 0 0 ... 0
っていう形になるから
X = A - B
じゃなくて
X * 10^(n-j-1) = A - B
になる。でも正しい答えが求まってたのはなぜだろう。
これで、A mod Q - B mod Q = 0のとき、
X * 10^(n-j-1) mod Q = A mod Q - B mod Q
X * 10^(n-j-1) mod Q = 0
で、Qは素数で、Qが2や5でない場合この合同式が成り立つためには、
X mod Q = 0
しかないからうまく答えが求まってた感じかな?

  • ソース
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

//Find the Multiples
public class AOJ1310 {

	Map<Integer, Integer> cnt;
	void inc(int m){
		if(cnt.containsKey(m))cnt.put(m, cnt.get(m)+1);
		else cnt.put(m, 1);
	}
	
	void run(){
		Scanner sc = new Scanner(System.in);
		for(;;){
			int n = sc.nextInt(), S = sc.nextInt(), W = sc.nextInt(), Q = sc.nextInt();
			if((n|S|W|Q)==0)break;
			int[] a = new int[n];
			int g = S;
			for(int i=0;i<n;i++){
				a[i] = (g/7) % 10;
				if((g&1) == 0)g>>=1;
				else g = (g>>1) ^ W;
			}
			int res = 0;
			if(Q==2){
				int e = 0;
				for(int i=n-1;i>=0;i--){
					if((a[i]&1)==0)e++;
					if(a[i]!=0)res+=e;
				}
			}
			else if(Q==5){
				int f = 0;
				for(int i=n-1;i>=0;i--){
					if(a[i]==0||a[i]==5)f++;
					if(a[i]!=0)res+=f;
				}
			}
			else{
				cnt = new HashMap<Integer, Integer>();
				inc(0);
				int ten = 1, m = a[n-1]%Q;
				inc(m);
				if(m==0 && a[n-1]!=0)res++;
				for(int i=n-2;i>=0;i--){
					ten = (ten*10)%Q;
					m = (a[i]*ten+m)%Q;
					if(a[i]!=0 && cnt.containsKey(m))res+=cnt.get(m);
					inc(m);
				}
			}
			System.out.println(res);
		}
	}

	public static void main(String[] args) {
		new AOJ1310().run();
	}
}