これTL2秒でいいと思うんだけどな。
http://codeforces.com/contest/703/problem/E
問題
N要素の数列A[i]が与えられる。
ここからいくつかの要素を選び、その積がKの倍数になるようにしたい。
条件を満たす要素の選び方の一例を求めよ。
ただし条件を満たす選び方が複数ある場合、選ぶ要素数を最小化せよ。
また選ぶ要素数がタイになる場合、要素の和が小さい方を選べ。
解法
A[i]の積を直接求めるのは大変だが、結局Kの倍数かどうかだけ分かればいいので積をすべて持つ必要はない。
GCD(K,A[i]のうち選んだものの積)だけを管理すればよい。
当然上記GCDの値はKの約数になる。
Kは10^12以下なので、Wikipediaなどの高度合成数の項を見ると約数は7000個もないことが分かる。
まずKの約数を列挙したうえで以下を求めよう。
f(n,k) := A[0...n]からいくつか要素を選択したとき、選択要素の積とKのGCDはKの約数のうち小さい方からk番目となるような選択の仕方のうちで、要素数最小・和が最小のものの(要素数,和,直前に選択した要素)のタプル
nは1000以下、kは7000以下で、要素は選ぶ選ばないの2通りしか遷移がないのでかろうじて1秒で間に合う。
DPを終えたらそれを巻き戻して選択した要素を列挙すればよい。
注意点として、(Kのk番目の約数をpとしたとき)f(n,k)から次のDPの過程でGCD(p*A[n],K)を求める処理がある。
この場合p*A[n]が64bitでもオーバーフローしかねない。
この場合、先にK/pを求めておいてp*GCD(A[n],K/p)とするとオーバーフローを回避できる。
vector<ll> enumdiv(ll n) { vector<ll> S; for(ll 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; } int N; ll K; ll A[10101]; unordered_map<ll,int> U; int num[1010][7000]; ll sum[1010][7000]; int from[1010][7000]; void solve() { int i,j,k,l,r,x,y; string s; cin>>N>>K; if(K==1) { FOR(i,N) cin>>A[i]; cout<<1<<endl; cout<<1+(min_element(A,A+N)-A)<<endl; return; } auto V=enumdiv(K); auto VR=V; reverse(ALL(VR)); FOR(i,V.size()) U[V[i]]=i, num[0][i]=101010; num[0][0]=0; FOR(x,N) { ll X; cin>>X; FOR(i,V.size()) { num[x+1][i]=num[x][i]; sum[x+1][i]=sum[x][i]; from[x+1][i]=i; } FOR(i,V.size()) if(num[x][i]<1010) { int zi=U[V[i]*__gcd(X,VR[i])]; if(num[x+1][zi]>num[x][i]+1 || (num[x+1][zi]==num[x][i]+1 && sum[x+1][zi]>sum[x][i]+X)) { num[x+1][zi]=num[x][i]+1; sum[x+1][zi]=sum[x][i]+X; from[x+1][zi]=i; } } } x = U.size()-1; if(num[N][x]>N) return _P("-1\n"); vector<int> R; for(y=N;y>0;y--) if(from[y][x]!=x) { R.push_back(y); x=from[y][x]; } _P("%d\n",R.size()); FORR(r,R) _P("%d ",r); _P("\n"); }
まとめ
この問題は最後のDPを復元するパート要る…?