WARush

SRMの結果とか、解けた問題のコードを書いていきます

SRM631 Div1 Medium "CatsOnTheLineDiv1"

考えたこと

ぬこグループが取るべき行動は2通りある。

1つは一匹ずつ散らばること。(パターン1)
f:id:Ekaing:20150508004037j:plain
複数ポイントが0になるため理想的な動き方。ネックはこの動き方をするための条件が厳しいこと。ぬこの数だけ空きポイントが必要だし、時間が十分にないといけない。

もう1つは他グループと併合すること。(パターン2)
f:id:Ekaing:20150508004908j:plain
1つめの動きを諦めざるを得ないときは、下手に散らばないでグループ全体で行動した方がよい。そして複数ポイントを最小化するために、別のグループを探して併合できれば併合したい。元々2匹いたところが100匹になったとしても、全く問題ない。

左のグループから順に考察していくことにすると・・・

パターン1の動きができるならば、その時はなるべく左に寄りたい。こうすることで、次(1つ右)のグループのパターン1の動きをなるべくやり易くさせてあげられる。また、次のグループがパターン2の動きをするとしても、ぬこの数が単数になるため右に寄ることのメリットは全くない。

パターン2の動きをするときは、なるべく右に寄りたい。こうすることで、次のグループがパターン2で動く時に併合させやすくなる。次のグループがパターン1の動きをするときに邪魔になるかと思いきや、邪魔になるくらい近い(時間内にぶつかる事ができる)なら併合すりゃいいじゃんということで、右によることによるデメリットが全くない。

DPで、単数複数それぞれで最も右のぬこの位置を状態として持っておき、単数の時はなるべく左、複数の時はなるべく右になるようにして、更新していけばよい。

ソースコード

// dp[i][j][0] := 右端が単数で最も左
// dp[i][j][1] := 右端が複数で最も右
// i...グループのインデックス
// j...今までに複数ポイントが何個できてしまったか
int dp[1050][1050][2];

class CatsOnTheLineDiv1 {
public:

    int getNumber(vector <int> pos, vector <int> count, int time) {
        int n = pos.size();

        // x昇順
        for (int i = 0; i < n; i++) {
            for (int j = 1; j < n; j++) {
                if (pos[j - 1] > pos[j]) {
                    int t = pos[j - 1];
                    pos[j - 1] = pos[j];
                    pos[j] = t;
                    t = count[j - 1];
                    count[j - 1] = count[j];
                    count[j] = t;
                }
            }
        }

        // dp初期化
        for (int i = 0; i <= n; i++) {
            for (int j = 0; j <= n; j++) {
                dp[i][j][0] = INF_R;
                dp[i][j][1] = INF_L;
            }
        }
        dp[0][0][0] = INF_L;
        
        // dp更新
        for (int id = 0; id < n; id++) {
            for (int cnt = 0; cnt < n; cnt++) {
                // 単数
                if (dp[id][cnt][0] != INF_R) {
                    int r = dp[id][cnt][0];
                    // パターン1の動きをする
                    //- 左に伸ばせる数
                    int canLeft = min(time, pos[id] - r - 1);
                    //- 右に伸びるべき数
                    int needRight = count[id] - canLeft - 1;
                    if (needRight <= time) {
                        // 時間内にいけるなら動く
                        dp[id + 1][cnt][0] = min(dp[id + 1][cnt][0], pos[id] + needRight);
                    }
                    // パターン2の動きをする
                    //- なるべく右へ
                    dp[id + 1][cnt + 1][1] = max(dp[id + 1][cnt + 1][1], pos[id] + time);
                }

                // 複数
                if (dp[id][cnt][1] != INF_L) {
                    int r = dp[id][cnt][1];
                    // パターン1の動きをする
                    //- 左に伸ばせる数
                    int canLeft = min(time, pos[id] - r - 1);
                    //- 右に伸びるべき数
                    int needRight = count[id] - canLeft - 1;
                    if (needRight <= time) {
                        // 時間内にいけるなら動く
                        dp[id + 1][cnt][0] = min(dp[id + 1][cnt][0], pos[id] + needRight);
                    }
                    // パターン2の動きをする
                    //- 併合できるなら併合
                    if (pos[id] - time <= r) {
                        dp[id + 1][cnt][1] = max(dp[id + 1][cnt][1], r);
                    }
                    // なるべく右へ
                    dp[id + 1][cnt + 1][1] = max(dp[id + 1][cnt + 1][1], pos[id] + time);
                }

                
            }
        }

        // dp結果
        for (int i = 0; i <= n; i++) {
            if (dp[n][i][0] != INF_R) {
                return i;
            }
            if (dp[n][i][1] != INF_L) {
                return i;
            }
        }
        return n;
    }
};