SRM 403 devision2 Hard:
簡単に要約すると、
4と7のみから構成される数字をlucky numberと呼ぶ。
整数nが与えられたときlucky numberのみを足し合わせてnをつくることができる場合は、そのlucky numberを列挙せよ。ただし、そのような組み合わせが複数個ある場合は、個数が最小のもの、さらにそれが複数個ある場合は、辞書順で最小になるものを求めよ。
詳しくは、上のTopCoderのサイトを参照ください。
ここでポイントは、
- lucky numberを探索してキャッシュすること
- nがlucky numberのみで構成できるかどうかDPを使って判定すること
- 構成出来る場合は、その解を問題の定義どおりに列挙すること
2.までは、出来たんだけど・・・
赤い人のコードを参考に復習しました。
まず、1.ですが再帰で書くとかなりスマートに書けます。
2.は部分和問題と同等の方法で書けます。dp[i]には整数iを構成するのに必要なlucky numberの最小値をキャッシュします。(私はdp[i]にiを構成するために必要なluckyu numberをvectorにぶち込んでキャッシュしてました。これだと、メモリ制限や時間制限でoutになりました。。)
3.は2.で作った配列を元に経路復元みたいなことをやります。その際、なるべく小さい数をたくさん取るように(辞書順で小さくなるように)greedyで復元します。
以下、(赤い人を参考に)私が書いたコードを張っておきます。
int dp[1000001];
class TheSumOfLuckyNumbers {
public:
void makeLucky(vector<int>&A, int x, int n) {
if (x)
A.PB(x);
if (x * 10 + 4 <= n)
makeLucky(A, x * 10 + 4, n);
if (x * 10 + 7 <= n)
makeLucky(A, x * 10 + 7, n);
}
vector<int> sum(int n) {
VI A;
makeLucky(A, 0, n);
SORT(A);
// DP
EACH(itr, A) {
dp[*itr] = 1;
REP(i, n+1) {
if (!dp[i])
continue;
if (i + *itr > n)
continue;
if (!dp[i + *itr] || dp[i + *itr] >= dp[i] + 1)
dp[i + *itr] = dp[i] + 1;
}
}
VI ret;
int pos = n;
// Greedy
EACH(itr, A) {
while (dp[pos] - dp[pos - *itr] == 1 && pos) {
if (!dp[pos - *itr] && pos - *itr)
break;
ret.PB(*itr);
pos -= *itr;
if (pos - *itr < 0) break;
}
if (!pos)
break;
}
return ret;
}
};
あと、気付きですが、TopCoderの過去問を解くのはPOJを解くよりも良いかもしれません。
その理由は、
- 問題のレベル分けがきちんとなされている
- 赤い人のコードや解説(editorial)が読める
- すべての問題が種類別にカテゴライズされている。
3.のカテゴライズは今日気付きました。
Problem Archiveのところにcategoryってのがあるので、そこで「DP」とか「String manipulation」とか「recursive」とか選択できるみたいです。
0 件のコメント:
コメントを投稿