半分全列挙(DP)(ABC054D - Mixing Experiment)
ところでD問題って(n年前の感覚としては)普通に解けるような感覚でいたけど結構厳しい
精進をn年もサボっていたせいですね
二通りのやり方で解きました。
半分全列挙
全探索すると2^40かかる。半分全列挙すると2^21かかる。難しいこと考えずにこれで良いか…
具体的には、の時となるので、これを半分ずつ全ての可能性求めておき、半分を定めればもう半分の選び方は二分探索でいける。
計算量はだいたい10^7~10^8
何個もWAを出してしまった。半分全列挙の時はその半分の中から使わないということを表すコスト0、値0を入れるんだけど、今回ソートの関係で、半分ずつが成り立つ場合強制的にそれが選ばれてしまう場合があり、実際はCを作ることが可能なのに-1を出力してしまう例外がなかなか取り除けなかった為である。(説明が適当なのですがコードを見ればわかります)
if(ite+1<H2.size()&&H1[i].second+H2[ite+1].second!=0&&H1[i].first+H2[ite+1].first==0){ ans=min(ans,H1[i].second+H2[ite+1].second); }
を挿入して解決。
#include <stdio.h> #include <stdlib.h> #include<math.h> #include<algorithm> #include<vector> #include<queue> #include<string> #include<set> #include<cstring> using namespace std; #define rep(i,n) for(int i=0;i<n;i++) #define INF 1001001001 #define LLINF 1001001001001001001 #define mp make_pair #define pb push_back #define LLIandI pair<long long int , int> #define ll long long int main(void){ int N,Ma,Mb; scanf("%d %d %d",&N,&Ma,&Mb); int A[50],B[50],C[50]; rep(i,N){ scanf("%d %d %d",&A[i],&B[i],&C[i]); } //Asum*Mb-Bsum*Ma=0 vector< pair<int, int> > H1; vector< pair<int, int> > H2; rep(i,1<<(N/2)){ //0...N/2-1 (N/2) int ABh=0,Ch=0; rep(j,N/2){ ABh+=Mb*A[j]*( (i>>j)%2 ); ABh-=Ma*B[j]*( (i>>j)%2 ); Ch+=C[j]*( (i>>j)%2 ); } H1.pb(mp(ABh,Ch ) ); } rep(i,1<<(N-N/2) ){//N/2...N-1 (N-N/2) int ABh=0,Ch=0; rep(j,(N-N/2)){ ABh+=Mb*A[j+N/2]*( (i>>j)%2 ); ABh-=Ma*B[j+N/2]*( (i>>j)%2 ); Ch+=C[j+N/2]*( (i>>j)%2 ); } H2.pb(mp(ABh,Ch) ); } sort(H1.begin(),H1.end());sort(H2.begin(),H2.end()); int ans=1001001001; rep(i,H1.size()){ int ite=lower_bound(H2.begin(),H2.end(),mp(-H1[i].first,-1) )-H2.begin(); if(ite<H2.size()&&H1[i].second+H2[ite].second!=0&&H1[i].first+H2[ite].first==0){ ans=min(ans,H1[i].second+H2[ite].second); } if(ite+1<H2.size()&&H1[i].second+H2[ite+1].second!=0&&H1[i].first+H2[ite+1].first==0){ ans=min(ans,H1[i].second+H2[ite+1].second); } } if(ans==0||ans==1001001001)printf("%d\n",-1); else printf("%d\n",ans); }
動的計画法
思考停止半分全列挙してしまったけどなんかナップサックっぽいし動的計画法で他の解き方がありそう
解説をちらっと見たらやっぱりそっちが想定解ではあるのであとで考えます。
というわけでやりました。DPだということを確認して解いたので完全には自分で考えていません。
計算量は
(面倒臭いのでに実際の計算量は固定しています。)
またWAを量産したので調べてみたらやっぱり最初からいくらかを使わない時の例外処理ができていませんでした。まずに最終的になってしまった時は使っていないだけなのでダメです。さらに、最初からいくらかを使わない時については、コストが0の状況は継承しないことにしてから、とすることで、そこまでが0であった場合に対応できます。(これは解説を見てやっとわかりました…デバッグ力をあげたい)
#include <stdio.h> #include <stdlib.h> #include<math.h> #include<algorithm> #include<vector> #include<queue> #include<string> #include<set> #include<cstring> using namespace std; #define rep(i,n) for(int i=0;i<n;i++) #define INF 1001001001 #define LLINF 1001001001001001001 #define mp make_pair #define pb push_back #define LLIandI pair<long long int , int> #define ll long long //DPバージョン int main(void){ int N,Ma,Mb; scanf("%d %d %d",&N,&Ma,&Mb); int A[50],B[50],C[50]; int dp[41][41*11*11*2]; //dp[i][j]=m i番までの薬品を使ってM_a*b-M_b*a(絶対値N(=40)*Ma,Mb(=10)*max(a),max(b)(=10)=4000以下)=Jにしたときの最低のコストがmということを表す //Jについては、添字jについてJ=k-N*10*10とする。(kは0以上2*N*10*10以下 ) rep(i,N)rep(j,N*10*10*2+1)dp[i][j]=INF; rep(i,N){ scanf("%d %d %d",&A[i],&B[i],&C[i]); if(i==0){ dp[i][N*100]=0; dp[i][N*100+Ma*B[i]-Mb*A[i]]=C[i]; } } rep(i,N){ rep(k,2*100*N+1){ //i-1番めまでは一切使わない時の例外処理 dp[i][ N*100+Ma*B[i]-Mb*A[i] ]=min(C[i], dp[i][ N*100+Ma*B[i]-Mb*A[i] ] ); if(i<N-1){ if(dp[i][k]==0)continue;//i番目までは全く使っていないものについては上で処理するので継承しない int newJ=Ma*B[i+1]-Mb*A[i+1]; //i+1を使う時 dp[i+1][k+newJ]=min(dp[i][k]+C[i+1], dp[i+1][k+newJ] ); //i+1を使わない時 dp[i+1][k]=min(dp[i][k],dp[i+1][k]); } } } if(dp[N-1][N*100]>=INF||dp[N-1][N*100]<=0)printf("%d\n",-1); else printf("%d\n",dp[N-1][N*100]); }
オマケ