WARush

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

SRM678 Div1 Easy "ANewHope"

解法

firstWeekからlastWeekへ、indexが遡るように、一番離れているようなシャツが、lastWeekを再現するのにボトルネックになっている。だからそのシャツをとにかく最優先に考えて、洗って乾いたら直ぐに着ていく。

他のシャツは最優先シャツのスケジュールを尊重して邪魔しないように。

ソースコード

class ANewHope {
    public:
    int count(vector<int> firstWeek, vector<int> lastWeek, int D) {
        int n = firstWeek.size();

        int move = 0;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (firstWeek[i] == lastWeek[j]) {
                    move = max(move, i - j);
                }
            }
        }
        return move / (n - D) + ((move % (n - D) == 0) ? 0 : 1) + 1;
    }
};

SRM679 Div1 Easy "FiringEmployees"

解法

dfsして貪欲に。
自分を含めてマイナスになったら自ら首を切って0を返して会社に貢献しよう。(部下を道連れに)

ソースコード

vector<int> G[2550];
int P[2550];

class FiringEmployees {
    public:
    int fire(vector<int> m, vector<int> s, vector<int> p) {
        int n = m.size();

        for (int i = 0; i <= n; i++) {
            G[i].clear();
        }

        P[0] = 0;
        for (int i = 1; i <= n; i++) {
            G[m[i - 1]].push_back(i);
            P[i] = p[i - 1] - s[i - 1];
        }

        return solve(0);
    }

    int solve(int cur) {
        int res = 0;
        for (int i = 0; i < G[cur].size(); i++) {
            int t = solve(G[cur][i]);
            if (t >= 0) {
                res += t;
            }
        }
        res += P[cur];
        return max(res, 0);
    }
};

SRM680 Div1 Easy "BearFair"

解法

こちらをチラ見しました(dpの作り方だけ。・・・・まあほとんど解答のようなものだけど・・・)
mayokoex.hatenablog.com

ありがとうございましたm(_ _)m

事後

チラ見してなお実装に75分かかるとはいったい・・・ウゴゴゴ・・・

貰うdpひさびさに実装したぞ~

ソースコード

bool dp[1010][55][55];

class BearFair {
    public:
    string isFair(int n, int b, vector<int> upTo, vector<int> quantity) {

        // dp初期化
        for (int i = 0; i <= b; i++) {
            for (int o = 0; o <= n; o++) {
                for (int d = 0; d <= n; d++) {
                    dp[i][o][d] = false;
                }
            }
        }
        dp[0][0][0] = true;

        // dp更新
        for (int i = 1; i <= b; i++) {
            for (int o = 0; o <= n; o++) {
                for (int e = 0; o + e <= n; e++) {
                    bool ok = true;
                    // check!!!
                    for (int j = 0; j < upTo.size(); j++) {
                        if (i < upTo[j]) {
                            // quantity兄貴より上回ることは許さん
                            if (o + e > quantity[j]) ok = false;
                        } else if (i == upTo[j]) {
                            // quantity兄貴と同じでないと許さん
                            if (o + e != quantity[j]) ok = false;
                        } else {
                            // quantity兄貴より下回ることは許さん
                            if (o + e < quantity[j]) ok = false;
                        }
                    }
                    if (!ok) continue;
                    // printf("2 %d %d %d\r\n", i, o, e);
                    if (i % 2 != 0) {
                        // iが奇数だ!
                        dp[i][o][e] = dp[i - 1][o - 1][e] | dp[i - 1][o][e];
                    } else {
                        // iが偶数だ!
                        dp[i][o][e] = dp[i - 1][o][e - 1] | dp[i - 1][o][e];
                    }
                }
            }
        }

        // 答え算出
        bool ans = false;
        for (int i = 0; i <= b; i++) {
            ans |= dp[i][n / 2][n / 2];
        }
        return ans ? "fair" : "unfair";
    }
};

SRM681 Div1 Easy "FleetFunding"

解法

宇宙船を作る数を決め打ちする。
するとどうだろう、ワークショップを貪欲に使用することが出来る。
今、パーツpを作りたいと思った時、パーツを作ることができ(a[i] <= p <= b[i])、かつ期限が一番最初に切れてしまう(min(b[i]))なワークショップiを優先的に使用していくのがベスト。

決め打ちの数はにぶたんで。

ソースコード

// ワークショップをクラスに
class W {
public:
    int num;
    int L;
    int R;

    W (int _num, int _L, int _R) {
        num = _num;
        L = _L;
        R = _R;
    }

    // 最後のパーツの昇順でソートされるようにする
    bool operator< (const W& other) const {
        if (R < other.R) {
            return true;
        } else {
            return false;
        }
    }
    bool operator> (const W& other) const {
        if (R > other.R) {
            return true;
        } else {
            return false;
        }
    }
};

int N;
int M;
vector<int> K;
vector<int> A;
vector<int> B;

class FleetFunding {
    public:
    int maxShips(int m, vector<int> k, vector<int> a, vector<int> b) {
        N = k.size();
        M = m;
        K = k;
        A = a;
        B = b;

        // にぶたんで機数を決め打ち
        int L = 0;
        int R = 1000000 * 50 + 1;
        while (L + 1 < R) {
            int mi = (L + R) / 2;
            if (solve(mi)) {
                L = mi;
            } else {
                R = mi;
            }
        }
        return L;
    }

    bool solve(int need) {
        vector<W> ws;
        for (int i = 0; i < N; i++) {
            ws.push_back(W(K[i], A[i], B[i]));
        }
        sort(ws.begin(), ws.end());

        for (int p = 1; p <= M; p++) {
            int rem = need;
            for (int i = 0; i < N; i++) {
                W w = ws[i];
                if (ws[i].R < p || p < ws[i].L) continue;
                if (rem < w.num) {
                    // 余力アリ
                    ws[i].num -= rem;
                    rem = 0;
                } else {
                    // 余力ナシ
                    rem -= w.num;
                    ws[i].num = 0;
                }
            }
            if (rem != 0) return false;
        }

        return true;
    }
};

SRM682 Div1 Easy "SmilesTheFriendshipUnicorn"

考えた解法

普通にdfsで探索すると計算量がやばい。
特に下図のような構成となっているグラフで、赤い頂点から探索を進めた日にゃあスタート頂点1つにつき計算量がO(M^2)になる。やばい。
f:id:Ekaing:20161002230052p:plain

あれ待てよ・・・赤い頂点って・・・そんなに無くね?・・・いけるんじゃね?

いやいや、2000のルートだけグラフを分割して
f:id:Ekaing:20161002230712p:plain
こーんなグラフで来やがるかもしれんぞ・・

あれでも45(2000のルート)^3で計算終わるな・・・

・・・

普通にdfsで探索しても計算量はいける。

ソースコード
int N;
vector<int> G[2020];
bool use[2020];

class SmilesTheFriendshipUnicorn {
    public:
    string hasFriendshipChain(int _N, vector<int> A, vector<int> B) {
        N = _N;

        // グラフ作成
        for (int i = 0; i < N; i++) {
            G[i].clear();
        }
        for (int i = 0; i < A.size(); i++) {
            G[A[i]].push_back(B[i]);
            G[B[i]].push_back(A[i]);
        }

        // スタートとなる頂点で回す
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                use[j] = false;
            }
            // dfsで探索
            if (dfs(i, 1)) return "Yay!";
        }
        return ":(";
    }

    bool dfs(int cur, int num) {
        if (num == 5) return true;
        use[cur] = true;
        for (int i = 0; i < G[cur].size(); i++) {
            int next = G[cur][i];
            if (use[next]) continue;
            if(dfs(next, num + 1)) return true;
        }
        use[cur] = false;
        return false;
    }
};

Editorial

https://apps.topcoder.com/wiki/display/tc/SRM+682

この問題はいくつかのアプローチがある。1つに、「a->b->c->d」というような4つの異なる頂点のパスを見つけ出す方法を考えればよいと、問題を簡単にしてしまう方法がある。このようなパスを見つけることができたら、あとはそこに繋がる頂点iを見つける必要があるだけである。4つの長さのパスを全て列挙したとして、aに接続したiを探すことになる。それはa, b, c,そしてdとは異なる最初のiであればよく、aと隣接した頂点を走査すれば、高々5回試せばよい。なので、要する計算量は長さ4のシンプルパスの数に5を掛けたものとなる。

長さ4のシンプルパス

長さ4のシンプルパスは[a->b, b->c, c->d」のような3つの辺で構成される。この問題のグラフにおいて辺の数Mは高々2000であるため、「a->b, c->d」といった隣接した頂点のペアは高々O(M^2)である。それぞれにおいて、「b->c」といった中間の辺で繋がっているかを確認する。これは頂点が他の頂点と隣接しているかのマトリクスを予め計算しておけばO(1)となる。「a->b->c->d」が繋がっていることを確認できたら、最後に全ての頂点が異なることを確認する。この方法では、長さ4のシンプルパスを見つけるためにO(M^2)の計算量を要する。上記の通り、高々5回の計算で5つ目の頂点が見つかるため、全体の計算量もO(M^2)である。

SRM683 Div1 Easy "MoveStones"

Editorial

https://apps.topcoder.com/wiki/display/tc/SRM+683

まずは簡単に考えて、石の山が円状に並んでいないものとしよう。ちょうど、類題であるDiv2 Mediumでは石の山は直線状になっており、最後と最初の山が隣り合っていなかった。これの解法はO(n)の時間を要した。

円状の場合の解法は、石が山の間をn回以上移動する必要はなく、そのような動きは冗長である。なので、すべての隣り合う山において石を交換する必要はない。2つの山を左端・右端として(隣り合っていないとみなして)Div2バージョンのように解くことができる。よって、全てのO(n)の候補を試し、その最小値を返せばよい。

事後

「石が山の間をn回以上移動する必要はない」ってのがよく分からん!

ソースコード

class MoveStones {
    public:
    long long get(vector<int> a, vector<int> b) {
        int n = a.size();

        // おかしいやつ検出
        long long sa = 0, sb = 0;
        for (int i = 0; i < n; i++) {
            sa += a[i], sb += b[i];
        }
        if (sa != sb) return -1;

        // こっからおかしくないやつ
        long long ans = 1e18;
        // start地点を決めて回す(直線状と考える)
        for (int start = 0; start < n; start++) {
            long long cnt = 0;
            long long over = a[start] - b[start];
            for (int i = 0, cur = next(start, n); i < n - 1; i++, cur = next(cur, n)) {
                cnt += abs(over);
                over += a[cur] - b[cur];
            }
            // 最小をとっていく
            ans = min(ans, cnt);
        }

        return ans;
    }

    // 次のindexを返す
    int next(int i, int n) {
        return i + 1 == n ? 0 : i + 1;
    }
};

SRM684 Div1 Easy "CliqueParty"

解法

配列を昇順にソートしておいて、サブセットに含める最小値a[L]と最大値a[R]の2つを決め打ちする。
すると、Dの最大値は「a[R] - a[L]」に固定される。Dの最小値も「Dの最大値 / k」に固定される。
あとはa[L + 1]からa[R - 1]まで順に走査し、最近使用した左のものとの差、およびa[R]との差がDの最小値以上のものを貪欲に選んでいけばよい。

ソースコード

class CliqueParty {
    public:
    int maxsize(vector<int> a, int k) {
        int n = a.size();
        sort(a.begin(), a.end());
        int ans = 2;
        for (int L = 0; L < n; L++) {
            for (int R = L + 1; R < n; R++) {
                // (i, j)の範囲内とする。
                int ma = a[R] - a[L];
                int mi = ma / k + (ma % k == 0 ? 0 : 1);

                // 貪欲に行こう
                int cnt = 0;
                int pre_val = a[L];
                for (int i = L + 1; i < R; i++) {
                    if (mi <= a[i] - pre_val && mi <= a[R] - a[i]) {
                        cnt++;
                        pre_val = a[i];
                    }
                }

                if (ans < 2 + cnt) {
                    ans = 2 + cnt;
                }
            }
        }
        return ans;
    }
};