hogecoder

tsutaj 競技プログラミングの記録

TopCoder SRM707 Div 2 Med: StepsConstruct

正答率低いのはバグりやすいせいかな。発想自体はそんなに難しくない。

問題概要

原文 → TopCoder Statistics - Problem Statement

 n \times m の盤面があり、. は通行可能な座標、# は通行不可能な座標を表す。座標の移動は上下左右の方向のみ許されている。左上の座標からスタートし、右下の座標にちょうど  k ステップで到達できるルートが存在するならば、そのルートを出力せよ。なお、同じ座標に何度移動しても構わないものとする。

解説

まず普通に BFS をやってみて、右下に行くまでに最短で何ステップ必要かを求めましょう。この時点で INF になったり  k を超えてたりしていると明らかに無理なので弾きます。

次に、 k と最短ステップの偶奇を調べましょう。これが一致していないときも弾きます。

経路復元は、ゴールから再度 BFS していくのがやりやすいと思います。ゴールからたどれば、今いる座標の隣のどこかに (スタートから今いる座標に行くまでのステップ数 -  1 ) の座標が必ず存在するので、経路復元が可能になります。

ここまで来たら、ステップ数が  k になるように適当に増やせば終わりです。手っ取り早いやり方としては、右下の座標の隣のドットと右下の座標に交互に移動し続けるのが良いでしょう。

ソースコード

int dy[]={0, 0, 1, -1};
int dx[]={1, -1, 0, 0};
string dir = "DURL";

int rec[100][100];
class StepsConstruct {
    public:
    string construct(vector<string> board, int k) {
        int n = board.size(), m = board[0].size();
        rep(i,0,n) rep(j,0,m) rec[i][j] = INF;
        rec[0][0] = 0;
        queue<pii> q;
        if(board[0][0] == '.') q.push(pii(0,0));

        while(!q.empty()) {
            pii t = q.front(); q.pop();
            rep(i,0,4) {
                int x = t.fr + dx[i];
                int y = t.sc + dy[i];
                if(x < 0 || x >= n || y < 0 || y >= m) continue;
                if(board[x][y] == '#') continue;
                if(rec[x][y] <= rec[t.fr][t.sc] + 1) continue;
                rec[x][y] = rec[t.fr][t.sc] + 1;
                q.push(pii(x,y));
            }
        }

        if(rec[n-1][m-1] == INF || rec[n-1][m-1] > k) return "";
        if(k % 2 != rec[n-1][m-1] % 2) return "";

        string ret = "";
        int dist = rec[n-1][m-1];
        q.push(pii(n-1, m-1));
        int repdir = -1;
        while(!q.empty()) {
            pii t = q.front(); q.pop();
            rep(i,0,4) {
                int x = t.fr + dx[i];
                int y = t.sc + dy[i];
                if(x < 0 || x >= n || y < 0 || y >= m) continue;
                if(rec[x][y] != dist - 1) continue;
                if(repdir == -1) repdir = i;
                ret += dir[i ^ 1];
                dist--;
                q.push(pii(x, y));
            }
        }
        reverse(ret.begin(), ret.end());
        while(ret.size() != k) ret = ret + dir[repdir] + dir[repdir ^ 1];
        return ret;
    }
};

今回はすんなり行けたけど、この手の問題って本番書いたらバグりそうでこわい。

AOJ DPL_1_G: Knapsack Problem with Limitations

個数制限付きナップザック問題です。

問題概要

原文 → Knapsack Problem with Limitations | Aizu Online Judge

価値  v_i で重さ  w_i であるような  N 種類の品物と、容量が  W のナップザックがある。

 i 番目の品物は  m_i 個まで使用できる。

ナップザックの容量を超えないように品物を入れた時の価値の最大値を求めよ。

解説

各品物について、品物を  1 個入れたとき、 2 個入れたとき・・・と愚直に  1 ずつ増やして考えていくと計算量的に間に合いません。

そこで、繰り返し二乗法のように(厳密には少し違うけど)、取る数を  2 倍ずつ増やしていくことで考えていきましょう。

例えば品物が  13 個ある場合は、この場合ですと

  • 品物を  1 個入れたとき
  • 品物を  2 個入れたとき
  • 品物を  4 個入れたとき
  • 品物を  6 個入れたとき

をそれぞれ計算するというわけですね。これで計算量が  O(NW \log m) となり間に合います。

ソースコード

for 文の向きに気をつけましょう。

signed main() {
    int N, W; cin >> N >> W;
    int v[110], w[110], m[110];
    rep(i,0,N) cin >> v[i] >> w[i] >> m[i];

    int dp[10010] = {};
    rep(i,0,N) {
        // take key good(s) each
        for(int k=0; m[i]>0; k++) {
            int key = min(m[i], (int)(1<<k));
            m[i] -= key;
            for(int j=W; j>=key*w[i]; j--) {
                // do not take or take
                dp[j] = max(dp[j], dp[j-key*w[i]] + key*v[i]);
            }
        }
    }
    int ans = 0;
    repq(i,0,W) ans = max(ans, dp[i]);
    cout << ans << endl;
    return 0;
}

AOJ 1335: Equal Sum Sets

たとえ簡単な DP でも一発で通ると嬉しいよね。

問題概要

原文 → Equal Sum Sets | Aizu Online Judge

 n 以下の相異なる自然数  k 個の合計が  s となる組み合わせの総数を求めよ。

  •  1 \leqq n \leqq 20
  •  1 \leqq k \leqq 10
  •  1 \leqq s \leqq 155

解説

 n 以下の各数字  i について、 i を使うときと使わないときを考えてみます。

  •  i を使うときは、合計が  i 増え、使う数字の数は  1 増える
  •  i を使わないときは、合計は変わらず、使う数字の数も変わらない

これを用いて DP を書きます。計算量  O(nks) です。

ソースコード

signed main() {
    int N, K, S;
    while(cin >> N >> K >> S, N || K || S) {
        // dp[i][j][k] := i 以下の数字を j 個使って k を作る組み合わせ
        int dp[25][25][255] = {};
        dp[0][0][0] = 1;
        repq(i,1,N) {
            repq(j,0,K) {
                repq(k,0,S) {
                    // i を使うと 合計は i 増える
                    if(k - i >= 0 && j != 0) dp[i][j][k] += dp[i-1][j-1][k-i];

                    // i を使わないと 合計は変わらない
                    dp[i][j][k] += dp[i-1][j][k];
                }
            }
        }
        cout << dp[N][K][S] << endl;
    }
    return 0;
}

にしてもAOJ-ICPCでこれ150点なのかー。・・・と思ってたけどよく考えたら  n が20までしかないからビットで全列挙できるか。