はまやんはまやんはまやん

hamayanhamayan's blog

Coins [AtCoder Grand Contest 018 C]

http://agc018.contest.atcoder.jp/tasks/agc018_c

解説

http://agc018.contest.atcoder.jp/submissions/1450483
解説放送の副読本的内容です。

Cを全て取る状態からスタートする。
すると、Aに変えるとA-Cだけ増加、Bに変えるとB-Cだけ増加するので、A-=C, B-=Cしておく。
ここで、A[i]-B[i]の昇順に並べておくと、前半からBをY個取る、後半からAをX個取るのが最適になる。
なぜ最適かは解説放送が詳しい。
AA,BBをA[i]-B[i]でソート済みのA,Bとする。

ここで、事前に以下のDPを用意しておく。
dp1[i] := 配列BBの[0,i]からY個取った和の最大値
dp2[i] ;= 配列AAの[i,N-1]からX個取った和の最大値
これを求めるにはpriority_queueを使うと良い。

関数dodp1の中身を少しだけ詳しく解説しておく。
c++のpriority_queueは大きい順から取り出す仕組みになっている。
今回は小さい順で取り出す目的で使いたいので、その場合はマイナスを付けて入れることで解決できる。
キューのサイズがY個を超えた場合は、先頭から取り出して、それを総和から引く。
これを繰り返すことで今までの最大Y個がキューの中に残り続け、その和をsmとして、逐次更新していく。
これで高速にdp更新ができる(dodp2関数も同様)。

あとは、境界線kについて全探索して、前半後半の和の最小値とCの総和を足せば答え

typedef long long ll;
#define INF 1LL<<60
int X, Y, Z, N;
ll A[101010], B[101010], C[101010];
ll AA[101010], BB[101010];
//---------------------------------------------------------------------------------------------------
ll dp1[101010];
void dodp1() {
    priority_queue<ll> que;
    rep(i, 0, N) dp1[i] = -INF;
    ll sm = 0;
 
    rep(i, 0, N) {
        que.push(-BB[i]);
        sm += BB[i];
        if (Y < que.size()) {
            ll q = -que.top();
            sm -= q;
            que.pop();
        }
        if (que.size() == Y) dp1[i] = sm;
    }
}
//---------------------------------------------------------------------------------------------------
ll dp2[101010];
void dodp2() {
    priority_queue<ll> que;
    rep(i, 0, N) dp2[i] = -INF;
    ll sm = 0;
 
    rrep(i, N - 1, 0) {
        que.push(-AA[i]);
        sm += AA[i];
        if (X < que.size()) {
            ll q = -que.top();
            sm -= q;
            que.pop();
        }
        if (que.size() == X) dp2[i] = sm;
    }
}
//---------------------------------------------------------------------------------------------------
void _main() {
    cin >> X >> Y >> Z;
    N = X + Y + Z;
    rep(i, 0, N) cin >> A[i] >> B[i] >> C[i];
 
    ll ans = 0;
    rep(i, 0, N) {
        ans += C[i];
        A[i] -= C[i];
        B[i] -= C[i];
    }
 
    vector<int> v;
    rep(i, 0, N) v.push_back(i);
    sort(v.begin(), v.end(), [&](int a, int b) { return A[a] - B[a] < A[b] - B[b]; });
    rep(i, 0, N) {
        int j = v[i];
        AA[i] = A[j];
        BB[i] = B[j];
    }
 
    dodp1(); dodp2();
 
    ll ma = -INF;
    rep(k, 0, N - 1) ma = max(ma, dp1[k] + dp2[k + 1]);
 
    ans += ma;
    printf("%lld\n", ans);
}