SRM613 Div1 Medium - RandomGCD

http://compro.tsutajiro.com/archive/181015_incexc.pdf
(包除原理 解ける数え上げの範囲を広げよう - tsutaj)
今の俺が求めていたものはこれだった
包除原理を極め鯛

問題

L以上r以下の整数を重複を許してn個選んで数列を作る
全要素のgcdがkになるような数列はいくつあるか

1 <= n, k, L <= 10^9
L <= r <= 10^9
0 <= r-L <= 10^5

解法

数列の要素として使えるのはL以上r以下のkの倍数のみ
そこで L = ceil(L/k), r = floor(r/k) とし、
「L以上r以下の整数のみからなる長さnの数列のうち、全要素のgcdが1になるようなものの個数」
を求めることにする

f(g): L以上r以下の整数のみからなる長さnの数列のうち、全要素のgcdがg以上になるようなものの個数
とすると、答えは包除原理の要領で
Σ f(g) * (-1)^(gの素因数の個数) (g: 同じ素因数で2度以上割り切れないような整数)
となる

素因数で2度以上割り切れないような整数 っていう条件はどういうことかというと
例えばg=4のケースは、g=1で足されてg=2で引かれてちょうど相殺されているから、足したり引いたりする必要がない
(g=12なんかも同様に、g=1で+、g=2で-、g=3で-、g=6で+ と既に消えている)

するとgの上限はどこまで考えればいいかが問題になる
1からRまで全部試せればそれでいいけど制約的に無理
そこで↑のΣで足し合わせるとき、L以上r以下の範囲に条件を満たす数が2個以上存在するようなもののみ考えることにする
(∵ 1個しか存在しない場合は当然同じ数字xがn個続く数列になり、gcdはxになるため、(x=1でない限りは)どうせ答えに影響しないから)
こうするとgは1からr-Lまで考えれば十分になる
(∵ L以上r以下の範囲にr-L+1以上の整数の倍数が2度以上現れることはないため)
L=1のときのみ答えに+1してやればok

実装

pf(n)はnの素因数一覧を重複なしで返すようになってる
だからこれの積がnと一致しなかったら素因数の重複があるってことになるから、
↑で言ったように足しも引きもしない

accumulateがあるなら積バージョンもあるんじゃね?ってずっと思ってたんだけど、
ググってみたらコードの通り
accumulate(a.begin(), a.end(), s, multiplies<int>())
とか書くとs * (指定範囲の各要素の積)を計算してくれるらしい
滅多に使わないけど覚えておこう

足し引きする値は、L以上r以下のうちiで割り切れる整数の個数を
m = floor(r/k) - floor((L-1)/k)
として、
(m個の整数から1個選ぶのをn回繰り返す) - (同じ整数をn回繰り返す数列の個数)
= n^m - m
となる

class RandomGCD {
public:
	vector<int> pf(int n) {
		vector<int> res;
		for (int i = 2; i*i <= n; i++) if (n%i==0) {
			res.push_back(i);
			while (n%i==0) n /= i;
		}
		if (n!=1) res.push_back(n);
		return res;
	}

	int countTuples(int n, int k, int l, int r) {
		l = (l+k-1)/k, r = r/k;
		if (r<l) return 0;
		ll res = (l==1);
		reps(i,1,r-l+1) {
			vector<int> p = pf(i);
			if (i!=1 && accumulate(all(p),1,multiplies<int>())!=i) continue;
			int m = r/i - (l-1)/i;
			(res += (mop(m,n)-m) * (i==1 || p.size()%2==0 ? 1 : -1)) %= mod;
		}
		(res += mod) %= mod;
		return (int)res;
	}
};

https://community.topcoder.com/stat?c=problem_statement&pm=13016&rd=15846