kmjp's blog

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

CROC 2016 Elimination Round : F. Cowslip Collections

これは本番中には思いつかないな。
http://codeforces.com/contest/655/problem/F

問題

N要素の数列A[i]が与えられる。
また、ここに計Q要素が1つずつ追加される。
要素が追加されるたび、AからK要素を選んだ時のGCDの総和を求めよ。

解法

Editorialを見て回答。
処理中、GCDがaになる組み合わせがそれぞれg(a)個あったとすると、解はsum(a*g(a))である。
ではこのsum(a*g(a))を以下に高速に求めるか。

Aのうち、aを約数とするものの数がd(a)個あったとする。
すると一見 a*g(a) = a*{}_{d(a)} C_Kである。
ただ、これは同じ数を多重にカウントしている。
約数に6を持つAの要素は、当然2や3も約数に持つので、そこを多重カウントしないようにしなければならない。

本来、GCDがaとなる組み合わせが1つ増えるならば、解はaだけ加算する。
ただ上記のような約数の多重数え上げを防止するようにしたい。
そこでp[i] := (i以下の整数のうち、p[i]と互いに素でp_i未満の数を除いたもの)とする。
すると求める解は sum(a*g(a))ではなく sum(p_a*g(a)) = sum(p_a*{}_{d(a)} C_K)となる。

あとは要素を追加するたびに、求める値がどの程度変化するかを考えよう。
要素が約数qを持つ場合、約数にqを持つ要素数d(q)は1増えるので、 p_a*({}_{d(a)+1} C_K-{}_{d(a)} C_K)だけ増加する。
この差分を各約数毎に求め総和を取ればよい。

int N,K,Q;
int num[1010101];
ll co[1010101];
ll mo=1000000007;

ll combi(ll N_, ll C_) {
	const int NUM_=1000001;
	static ll fact[NUM_+1],factr[NUM_+1],inv[NUM_+1];
	if (fact[0]==0) {
		inv[1]=fact[0]=factr[0]=1;
		for (int i=2;i<=NUM_;++i) inv[i] = inv[mo % i] * (mo - mo / i) % mo;
		for (int i=1;i<=NUM_;++i) fact[i]=fact[i-1]*i%mo, factr[i]=factr[i-1]*inv[i]%mo;
	}
	if(C_<0 || C_>N_) return 0;
	return factr[C_]*fact[N_]%mo*factr[N_-C_]%mo;
}

template<typename V> vector<V> enumdiv(V n) {
	vector<V> S;
	for(V i=1;i*i<=n;i++) if(n%i==0) {S.push_back(i); if(i*i!=n) S.push_back(n/i); }
	sort(S.begin(),S.end());
	return S;
}


void solve() {
	int i,j,k,l,r,x,y; string s;
	
	for(i=1;i<=1000000;i++) co[i]=i;
	for(i=1;i<=1000000;i++) {
		for(j=i*2;j<=1000000;j+=i) co[j]-=co[i];
	}
	
	cin>>N>>K>>Q;
	while(N--) {
		cin>>x;
		auto v = enumdiv(x);
		FORR(r,v) num[r]++;
	}
	ll cur=0;
	for(i=1;i<=1000000;i++) (cur += co[i]*combi(num[i],K))%=mo;
	while(Q--) {
		cin>>x;
		auto v = enumdiv(x);
		FORR(r,v) {
			cur-=co[r]*combi(num[r],K)%mo;
			num[r]++;
			cur+=co[r]*combi(num[r],K)%mo;
		}
		cur=(cur%mo+mo)%mo;
		cout<<cur<<endl;
	}
}

まとめ

コードは意外とあっさり。