TopCoder SRM 615 Div1

こんにちはtatsyです。今回もSRMの解説やります。


Easy (250点)

問題

アメーバがいて、アメーバのエサとなるゼリーが列になって最大200個並んでいる。アメーバは自分と同じ大きさのゼリーと出会うと、そのゼリーを食べて大きさが2倍になる。このとき、任意の大きさのアメーバからスタートして、ゼリーの列を通過後に現れることのないアメーバの大きさの場合の数を答えよ。

解法

まず、単純なポイントとして、ゼリーの列に出てこない大きさのアメーバは現れることが可能。なので、ゼリーの大きさをsetにつっこんで、uniqueをとる。
そのそれぞれをゼリーの列に通して、結果の大きさをuniqueした列から削る。そうすれば答えがでる。

とても簡単な気がしたけど、以外にみんな答えるの遅かった。コンテスト終わった後、Pythonなら1行でかけるといっている人がいたw

以下正解のコードです。


#include <fstream>
#include <sstream>
#include <string>
#include <algorithm>
#include <functional>
#include <numeric>
#include <vector>
#include <map>
#include <set>
#include <bitset>
#include <queue>
#include <stack>
#include <utility>
#include <cstring>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cctype>
#include <climits>
using namespace std;

typedef long long lint;
typedef pair<lint,lint> pll;

#define REP(i,n) for(int i=0; i<n; i++)
#define REPP(i,s,e) for(int i=s; i<=e; i++)
#define ALL(a) (a).begin(), (a).end()
#define PRT(a) cerr << #a << " = " << (a) << endl
#define PRT2(a, b) cerr << #a << " = " << (a) << ", " << #b << " = " << (b) << endl

class AmebaDiv1 {
public:
    lint check(lint a, vector<int> X) {

        REP(i,X.size()) {
            if(a == X[i]) {
                a *= 2;
            }
        }
        return a;
    }

    int count(vector <int> X) {
        set<lint> s;
        REP(i,X.size()) {
            s.insert(X[i]);
        }

        vector<int> v(ALL(s));
        REP(i,v.size()) {
            lint res = check(v[i], X);
            set<lint>::iterator it = s.find(res);
            if(it != s.end()) {
                s.erase(it);
            }
        }
        return s.size();
    }
};

Medium (550点)

問題

ある散歩好きのシカが道路で結ばれた街を歩き回っている。今、街0からスタートして街n-1にちょうど時刻Tのときに到着したい。街全体は無向グラフのようになっていて、行き来にかかる時間は同じである。このような散歩が可能な場合にはPossible、不可能な場合にはImpossibleという文字列を返せ。

解法

まず、この問題内容からして、dp[街の番号][到達距離]というようなDP表を更新していくんだろうなと思った。ただ、この問題で与えられているTは最大が10^18もあるので、普通にDP表を持つことはできない。

残り10分くらいになって、道を通るときの所要時間の最大値が10000までだから、これをどうにか利用すれば小さい表で問題が解けるのかな?と思ったが、そのままタイムアップ。

この問題で気づくべきポイントは、「ある時間Tぴったりでn-1に到達できる」ということは「ある時間mで街0に戻ってくるような経路がある場合にS ≡ T (mod m)でn-1に到達できる経路が存在する」ことと同値だということです。

なぜなら両者はT – km = Sとなる0以上の整数kが存在することと同値だからです。なので、実際に持つべきDP表はずっと小さくて、せいぜいdp[51][10001]くらいを持てばよいことになります。

あとは経路のあまりを管理しながら、dpをしていくだけです。上のポイントはあまり直感的ではないですが、これさえ気づければ典型DP問題だったと思います。

以下がコードです。


#include <fstream>
#include <sstream>
#include <string>
#include <algorithm>
#include <functional>
#include <numeric>
#include <vector>
#include <map>
#include <set>
#include <bitset>
#include <queue>
#include <stack>
#include <utility>
#include <cstring>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cctype>
#include <climits>
using namespace std;

typedef long long lint;
typedef pair<lint,lint> pll;

#define REP(i,n) for(int i=0; i<n; i++)
#define REPP(i,s,e) for(int i=s; i<=e; i++)
#define ALL(a) (a).begin(), (a).end()
#define PRT(a) cerr << #a << " = " << (a) << endl
#define PRT2(a, b) cerr << #a << " = " << (a) << ", " << #b << " = " << (b) << endl

const lint INF = 1ll << 60;
const int N_MAX = 55;
vector<pll> G[N_MAX];
bool vis[N_MAX][20011];

typedef tuple<lint,lint,lint> Node;

class LongLongTripDiv1 {
public:
    bool check(lint mod, lint t, int n) {
        memset(vis, 0, sizeof(vis));
        priority_queue<Node, vector<Node>, greater<Node> > que;
        que.push(Node(0, 0, 0));
        while(!que.empty()) {
            Node nd = que.top();
            lint dist = get<0>(nd);
            lint pos  = get<1>(nd);
            lint rem  = get<2>(nd);
            que.pop();

            if(vis[pos][rem]) continue;
            vis[pos][rem] = true;

            if(pos == n-1 && dist <= t && rem == t % mod) return true;

            REP(i,G[pos].size()) {
                lint npos = G[pos][i].first;
                lint nrem = (rem + G[pos][i].second) % mod;
                lint ndst = min(INF, dist + G[pos][i].second);
                que.push(Node(ndst, npos, nrem));
            }
        }
        return false;
    }

    string isAble(int N, vector <int> A, vector <int> B, vector <int> D, long long T) {
        REP(i,N_MAX) {
            G[i].clear();
        }

        REP(i,A.size()) {
            G[A[i]].push_back(pll(B[i], D[i]));
            G[B[i]].push_back(pll(A[i], D[i]));
        }

        if(G[0].empty()) return "Impossible";
        return check(2 * G[0][0].second, T, N) ? "Possible" : "Impossible";
    }
};

まとめ

今回はEasyを10分くらいで早解きしたら過去最高順位を出して、レートが120近くも上がりました。今回は結構簡単な問題だった気がしたのですが、これは練習の成果ということでしょうかね?

TCOが明日に控えているので、この調子でレートを上げていきたいところです。まぁ、レートも重要ですが、そろそろMediumを通したいところですね。

というわけで、今日はここまでにします。最後までお読みいただきありがとうございました。