mayoko’s diary

プロコンとかいろいろ。

yukicoder No.54 Happy Hallowe'en

ツボクラさんがSRM 502のmedはこの問題の類題で解いたとつぶやいていたので解いてみました。
こっちの方が3倍位難しく感じる…

解法

medと同じように何らかの基準を見つけ,その基準通りもらえる家の順番を並べ,dpしていく。
しかしこの問題の「基準」を発見するのが結構難しい。結論から言うとV_i+T_iが小さい順にt並べれば良い。以下でその理由を説明する。

pとqのどちらを先に並べるかを比べてみる。すなわち,

①..., p, q, ...
②..., q, p, ...

という2つの家への訪問の仕方について,どちらのほうが有利かを考える。

それぞれの場合で,p/qまで訪問している間に手に入れたお菓子の数をkとする。p, qの両方の家でお菓子をもらえない場合,pかqのどちらかでしかお菓子がもらえないことが確定している場合については考慮しなくて良い。
これは,今考えているのは「pとqの一方は選ぶことができるとして,どちらを先に選択するのが有利か」ということを考えている(前のmedの問題と同じように,訪問する家の集合が決まっているときにどのような順番にすべきかということを考えている)のでpとqどちらかしか訪問できないというのは対象外だからです。

さて,このときどちらの家を先に訪問するのが有利なのか考えてみます。①の場合は
 k+V_p < T_q \Leftrightarrow k < T_q-V_p
となれば両方の家からお菓子をもらえ,②の場合は
 k+V_q < T_p \Leftrightarrow k < T_p-V_q
となれば両方の家からお菓子をもらえます。

ここで①の条件のほうがゆるいための必要十分条件
 T_q-V_p > T_p-V_q \Leftrightarrow T_p+V_p < T_q+V_q
よって,上記の式が成り立つ時は
qを先に訪問してpとq両方の家からお菓子をもらえる\Rightarrow pを先に訪問してもpとq両方の家からお菓子をもらえる

ということが成り立ちます。よって,この場合はpを先に訪問するほうが両方の家でお菓子をもらえる可能性があり有利です。よって,T_p+V_pの値順にソートすれば良いということです。

以下ソースコード

const int MAXN = 10010;

struct present {
    int v;
    int t;
    present() {}
    present(int v, int t) : v(v), t(t) {}
    bool operator<(const present& rhs) const {
        return v+t < rhs.v+rhs.t;
    }
};

present p[MAXN];
bool dp[2][2*MAXN];

int main() {
    cin.tie(0);
    ios::sync_with_stdio(false);
    int N;
    cin >> N;
    for (int i = 0; i < N; i++) {
        int v, t;
        cin >> v >> t;
        p[i] = present(v, t);
    }
    sort(p, p+N);
    memset(dp, 0, sizeof(dp));
    dp[0][0] = true;
    for (int i = 0; i < N; i++) {
        int cur = i%2;
        int tar = cur^1;
        for (int t = 0; t <= MAXN; t++) {
            if (!dp[cur][t]) continue;
            dp[tar][t] = true;
            int tmp = t+p[i].v;
            if (t < p[i].t) dp[tar][tmp] = true;
        }
    }
    int ans = 0;
    for (int i = 0; i < 2*MAXN; i++) {
        if (dp[0][i] || dp[1][i]) ans = i;
    }
    cout << ans << endl;
    return 0;
}