kmjp's blog

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

yukicoder : No.2313 Product of Subsequence (hard)

計算量を落とす部分がちょっと珍しい。
https://yukicoder.me/problems/no/2313

問題

N要素の正整数列Aと、正整数Kが与えられる。
Aの部分列のうち、積がKの倍数になるのは何通りか。

解法

f(n,k) := Aのprefix n要素のうちいくつか部分列に選んだ時、積vとKのGCDがkとなる選び方の個数
を考えると、kで考えるべき値はd(K)個で良いため、O(NK)でこの問題は解ける。
ただ、N,Kが微妙に大きいのでこのままだとTLEする。

AのうちGCD(A[i],K)が一致するものはまとめて扱おう。

整数xにある値yを何回も掛けた場合、GCD(x*(y^q),K)の値はqがlog(K)以上あっても変わらない。
これを利用し、qは0~log(K)程度まで考えればよい。これにより計算量を減らすことができる。

int N;
ll K;
vector<ll> D;
const int mo=998244353;
ll from[1515],to[1515];
int num[1515];
int nex[1515][1515];

ll comb(ll N_, ll C_) {
	const int NUM_=400001;
	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;
}

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>N>>K;
	
	for(j=1;j*j<=K;j++) if(K%j==0) {
		D.push_back(j);
		if(j*j!=K) D.push_back(K/j);
	}
	
	sort(ALL(D));
	map<int,int> M;
	FOR(i,D.size()) M[D[i]]=i;
	
	FOR(i,D.size()) FOR(j,D.size()) nex[i][j]=M[__gcd(K,D[i]*D[j])];
	
	FOR(i,N) {
		ll a;
		cin>>a;
		num[M[__gcd((ll)K,a)]]++;
		
	}
	
	from[0]=1;
	FOR(i,D.size()) {
		ZERO(to);
		ll pat=1;
		FOR(j,num[i]) pat=pat*2%mo;
		ll a=1;
		for(j=0;j<=min(num[i],30);j++) {
			int b=M[a];
			ll p=comb(num[i],j);
			pat+=mo-p;
			FOR(x,D.size()) (to[nex[x][b]]+=p*from[x])%=mo;
			a=__gcd(K,a*D[i]);
		}
		a=__gcd(K,a*D[i]);
		int b=M[a];
		pat%=mo;
		FOR(x,D.size()) (to[nex[x][b]]+=pat*from[x])%=mo;
		swap(from,to);
	}
	
	cout<<from[D.size()-1]-(K==1)<<endl;
	
	
	
}

まとめ

こういう計算量の落とし方か…。