問題はこちら
No.385 カップ麺生活 - yukicoder
まずは「ちょうどk円で買えるカップ麺の最大個数」を求める。
これは要するに「Ci円玉でちょうどk円払う時、最大で何枚の硬貨が使えるか」なので、DPでできる。
(c.f.yukicoder No.247 線形計画問題もどき - メモ)
「金欠チャンス」を使用する時、その順序は関係なく、可能な限り多くの金欠チャンスを使うことが良いのは明らか。
よって「(M-素数)円で買えるカップ麺の個数」を全ての素数にわたって合計すれば良い。
「金欠チャンス」を使い果たしたら、最後は一番安いものを買い込めばいいので最後にM/min(Ci)を加える
#define max(p,q)(p>q?p:q) prime[2000]={2,3},s=2; void makeprime(int m){ int i,n; for(n=5;n<m;n+=2){ for(i=3;i*i<=n;i+=2)if(n%i==0)break; if(n%i)prime[s++]=n; } prime[s]=1e6;//番兵 } int c(int*a,int*b){return*a-*b;} int main(){ long ans=0; int i,j,m,n,a[30];ma[20010]; makeprime(m); scanf("%d %d",&m,&n); for(i=0;i<n;i++)scanf("%d",a+i); qsort(a,n,4,c); ma[0]=1; for(i=0;i<10000;i++)for(j=0;j<n;j++)if(ma[i])ma[i+a[j]]=max(ma[i]+1,ma[i+a[j]]); //ma[i]には、「i円で買える最大個数+1」を保存する for(i=0;prime[i]<m;i++)if(ma[m-prime[i]])ans+=ma[m-prime[i]]-1; printf("%ld",ans+m/a[0]); return 0; }
いつもどおり、DPする部分でわざわざCiを配列に保存する必要はない
min(Ci)も逐次更新すればいい
さらに素数の配列もいちいち作る必要はなさそう
q[20010],j,m,x=1e6,s,*p; main(i){ for(*q=scanf("%d%*d",&m);~scanf("%d",&j);x>j?x=j:0)for(p=q;p-q<1e4;p++)*p&&*p>=p[j]?p[j]=*p+1:0; //読み込みながらDP。Ciの最小値をxに保存 for(s=m/x;++i<m;s+=!q[m-i]|i-j?0:q[m-i]-1)for(j=1;++j<i&&i%j;); //素数判定しながら合計 x=!printf("%d",s); }
配列の添字は逆にしたほうがわかりやすい。つまりq[i]には「所持金がi円になった時に買える最大個数+1」を保存
こうすると1つめのループがだいぶ長くなってしまうけど、2つ目のループがmのカウントダウンで書け、変数iが要らなくなったのでトータルでは得。
ところで、合計する部分でq[i]-1ってあるけど、q[i]に保存するものの正負を逆にすればこれは~q[i]で書ける。これも縮みそう
ということで出来上がったものがこちら
q['~~'],m,x=1e6,*p; main(j){ for(scanf("%d%*d",&m),q[m]--;~scanf("%d",&j);x>j?x=j:0)for(p=q+'()';q-p--;p[j]&&p[j]<=*p?*p=p[j]-1:0); for(x=m/x;j=--m>1;x+=!q[m]|m-j?0:~q[m])for(;m%++j;); x=!printf("%d",x); }
Mが1のケースがあるからループ圧縮は無理そう
200B