1. 程式人生 > 其它 >Solution -「ZJOI 2010」「洛谷 P2570」貪吃的老鼠

Solution -「ZJOI 2010」「洛谷 P2570」貪吃的老鼠

\(\mathscr{Description}\)

  Link.

  有 \(n\) 塊乳酪,每塊乳酪出現時段為 \([s_i,t_i]\),體積為 \(V_i\);有 \(m\) 只老鼠要吃乳酪,第 \(i\) 只每秒吃 \(v_i\) 體積的乳酪。一塊乳酪不能同時被多於一隻老鼠吃,一隻老鼠不能同時吃多於一塊乳酪。求最小的 \(T\),使得所有 \(t_i\leftarrow t_i+T\) 後,老鼠可以把乳酪吃乾淨。

  多測,陣列組數 \(K\le 5\)\(n,m\le30\)\(1\le v_i,V_i\le10^5\),所有給定資料為整數,注意老鼠不一定需要在整數時刻改變進食狀態。

\(\mathscr{Solution}\)

  神妙網路流,給出了一個拆解“不能一對多”限制的建圖模型。

  顯然可以二分 \(T\),繼而離散化出 \(\mathcal O(n)\) 個乳酪存在狀態沒有改變的時段。對於跨度為 \(\Delta t\) 的時段,老鼠 \(i\) 的進食量最多是 \(v_i\Delta t\),但問題是,如果直接在乳酪-老鼠的二分圖上兩兩連邊,完全無法刻畫“一塊乳酪不能同時被多於一隻老鼠吃,一隻老鼠不能同時吃多於一塊乳酪”這一限制,這也是本題的難點。

  注意,網路流演算法本身不可能去描述“只能一對一”,我們需要從建圖修改,達到一種《乳酪“看上去”在被很多隻老鼠吃,實際上等價於在被一隻老鼠吃》的效果。怎麼辦?考慮差分。將老鼠按 \(v\)

升序排列,乳酪 \(i\) 連向老鼠 \(j\),容量為 \((v_j-v_{j-1})\Delta t\),表明我們可以把吃乳酪 \(i\) 的老鼠的速度從 \(v_{j-1}\) 提升到 \(v_j\),並保持一段時間,這段時間的長度作為流量,在網路流中將進行最優化抉擇。另一方面,每一種“升級”的可用次數是不同的:從 \(v_{j-1}\) 升級到 \(v_j\) 的總時間自然不超過 \((m-j+1)\Delta t\)。從規範化的角度,如果一塊乳酪選擇了一些不連續的升級,考慮到可用升級時間的單降性,一定能夠通過調整使之連續,使之能夠對應到實際問題的方案上去,正確性就得以保證了。

  複雜度 \(\mathcal O(K\log V\operatorname{Dinic}(nm,n^2m))\)

\(\mathscr{Code}\)

/*+Rainybunny+*/

#include <bits/stdc++.h>

#define rep(i, l, r) for (int i = l, rep##i = r; i <= rep##i; ++i)
#define per(i, r, l) for (int i = r, per##i = l; i >= per##i; --i)

const double EPS = 1e-7;
const int MAXN = 30, IINF = 0x3f3f3f3f;
int n, m, vol[MAXN + 5], st[MAXN + 5], ed[MAXN + 5], spd[MAXN + 5];

inline double dabs(const double x) { return x < 0 ? -x : x; }
inline int sign(const double x) { return dabs(x) <= EPS ? 0 : x < 0 ? -1 : 1; }

namespace FG {

const int MAXND = 2 * MAXN * MAXN + MAXN + 2;
const int MAXEG = MAXN + 2 * MAXN * MAXN + 2 * MAXN * MAXN * MAXN;
int S, T, B, ecnt = 1, head[MAXND + 5], dis[MAXND + 5], curh[MAXND + 5];
struct Edge { int to; double flw; int nxt; } graph[MAXEG * 2 + 5];

inline void clear() {
    ecnt = 1;
    rep (i, 0, B) head[i] = 0;
    B = -1;
}

inline void link(const int s, const int t, const double w) {
    // printf("%d %d %d\n", s, t, w);
    graph[++ecnt] = { t, w, head[s] }, head[s] = ecnt;
    graph[++ecnt] = { s, 0, head[t] }, head[t] = ecnt;
}

inline bool bfs() {
    static int que[MAXND + 5], hd, tl;
    rep (i, 0, B) dis[i] = IINF;
    dis[que[hd = tl = 1] = S] = 0;
    while (hd <= tl) {
        int u = que[hd++];
        for (int i = head[u], v; i; i = graph[i].nxt) {
            if (sign(graph[i].flw) && dis[v = graph[i].to] > dis[u] + 1) {
                dis[que[++tl] = v] = dis[u] + 1;
            }
        }
    }
    return dis[T] != IINF;
}

inline double augment(const int u, double iflw) {
    if (u == T) return iflw;
    double ret = 0.;
    for (int &i = curh[u], v; i; i = graph[i].nxt) {
        if (sign(graph[i].flw) && dis[v = graph[i].to] == dis[u] + 1) {
            double t = augment(v, std::min(iflw, graph[i].flw));
            graph[i].flw -= t, graph[i ^ 1].flw += t, iflw -= t, ret += t;
            if (!sign(iflw)) break;
        }
    }
    if (!sign(ret)) dis[u] = IINF;
    return ret;
}

inline double dinic() {
    double ret = 0;
    while (bfs()) {
        rep (i, 0, B) curh[i] = head[i];
        ret += augment(S, IINF);
    }
    return ret;
}

} // namespace FG.

inline bool check(const double x) {
    static double peri[MAXN * 2 + 5];
    int pcnt = 0, req = 0;
    rep (i, 1, n) peri[++pcnt] = st[i], peri[++pcnt] = ed[i] + x;
    std::sort(peri + 1, peri + pcnt + 1);

    FG::clear(), FG::S = ++FG::B;
    rep (i, 1, n) FG::link(FG::S, ++FG::B, vol[i]), req += vol[i];
    FG::T = ++FG::B;
    rep (i, 2, pcnt) if (sign(peri[i] - peri[i - 1])) {
        double dur = peri[i] - peri[i - 1];
        rep (j, 1, m) {
            FG::link(++FG::B, FG::T, dur * spd[j] * (m - j + 1.));
            rep (k, 1, n) {
                if (sign(st[k] - peri[i - 1]) <= 0
                  && sign(ed[k] + x - peri[i]) >= 0) {
                    FG::link(k, FG::B, dur * spd[j]);
                }
            }
        }
    }
    return !sign(FG::dinic() - req);
}

int main() {
    int T; scanf("%d", &T);
    while (T--) {
        scanf("%d %d", &n, &m);
        rep (i, 1, n) scanf("%d %d %d", &vol[i], &st[i], &ed[i]);
        rep (i, 1, m) scanf("%d", &spd[i]);
        std::sort(spd + 1, spd + m + 1);
        per (i, m, 1) spd[i] -= spd[i - 1];

        double l = 0., r = 3e6;
        while (l + EPS < r) {
            double mid = 0.5 * (l + r);
            if (check(mid)) r = mid;
            else l = mid;
        }
        printf("%f\n", l);
    }
    return 0;
}