kmjp's blog

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

yukicoder : No.636 硬貨の枚数2

ちょっと遅れました。
https://yukicoder.me/problems/no/636

問題

額面が非負整数kを用いて1*10^k円および5*10^k円の形となる硬貨が無限にあるとする。
今S円の支払いをして、そのお釣りをもらうことを考える。
支払いとお釣りが互いにやり取りする硬貨の枚数の総和を最小化するとき、それは何枚となるか。

解法

上の桁から順にDPしていく。
支払う側は

  • お釣りを出さないようにする、すなわち今見ている桁の金額ぴったりとなる硬貨を払う
  • お釣りを出す、すなわち今見ている桁の金額より1多い金額となる硬貨を払う

のいずれかを行う。
いずれにせよ今見ている桁は残金もお釣りも0になるので、以下次の桁を順次見ていく。

なお、お釣りを返すということは結局相手が一定の金額を払う点で同じなので、支払いもお釣りも同じ処理で扱える。
T=10^|S|-Sとすると、DPの過程で支払う側がSのsuffix相当の金額を払うか、お釣りを返す側がTのsuffix相当の金額のお釣りを返す(払う)とみなすことができる。

string S[2];
int N;

int memo[2][101010];

int hoge(int id,int d) {
	if(d>=N) return 0;
	if(memo[id][d]>=0) return memo[id][d];
	if(d==N-1) {
		if(S[id][d]>='5') return min(2+'9'-S[id][d],S[id][d]-'4');
		else if(S[id][d]>='1') return min(S[id][d]-'0',2+'4'-S[id][d]);
		return 0;
	}
	
	int ret=101010;
	
	if(S[id][d]>='5') ret=min(ret,S[id][d]-'4'+hoge(id,d+1));
	ret=min(ret,S[id][d]-'0'+hoge(id,d+1));
	
	if(S[id][d]>='5') ret=min(ret,1+'9'-S[id][d]+hoge(id^1,d+1));
	else if(S[id][d]>='1') ret=min(ret,1+'4'-S[id][d]+hoge(id^1,d+1));
	else if(S[id][d]>='0') ret=min(ret,1+hoge(id^1,d+1));
	
	return memo[id][d]=ret;
}

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>S[0];
	while(S[0].back()=='0') S[0].pop_back();
	N=S[0].size();
	
	S[1]=S[0];
	int carry=0;
	for(i=N-1;i>=0;i--) {
		if(carry==0) {
			if(S[1][i]!='0') {
				carry=1;
				S[1][i]='0'+(10-(S[1][i]-'0'));
			}
		}
		else {
			S[1][i]='0'+(9-(S[1][i]-'0'));
		}
	}
	
	MINUS(memo);
	
	cout<<hoge(0,0)<<endl;
}

まとめ

シンプルな問題設定ながら割と手間取った。