MENU

Solution. LG6054 开门大吉 / 切糕模型

Description

$n$ 位选手参加比赛,共有 $m$ 套题,每套题包含 $p$ 个题目,第 $i$ 位选手答对第 $j$ 套题中第 $k$ 道的概率为 $f_{i,j,k}$。

每位选手选择一套题按顺序依次作答。若一位选手答对第 $i$ 题,会额外得到 $c_i$ 元奖励,注意每位选手只有答对了上一道题才会继续作答。

有 $y$ 条形如「第 $i$ 位选手的套题编号必须至少比第 $j$ 位的大 $k$」的限制。

给每一位选手分配一套题(不同选手可以相同),使所有人的期望奖励和最小,你需要求出这个最小值。

$1\le n,m,p\le 80,0\le y\le 10^3$

Solution

本题是经典的切糕模型,一般化的切糕模型如下:

有 $n$ 个整数变量 $x_i$,$x_i\in[1,m]$。$x_i$ 取 $j$ 的代价为 $a_{i,j}$。

有若干限制,每个限制形如 $x_u-x_v\le k$ 或 $x_u-x_v\ge k$。

给每个变量赋值,最小化总代价。

考虑使用最小割解决这类问题,基本的思想是,建出 $n$ 条长 $m+1$ 的链,$i\to i+1$ 的边权为当前变量取 $i$ 的代价,割掉这条边即表示让当前变量取 $i$,$S$ 向 $1$ 连流量为 $\inf$ 的边,$m+1$ 向 $T$ 连流量为 $\inf$ 的边。

image-20230118191513198

流网络大概长这样,编号是随便标的。

接下来我们考虑限制如何描述。

如果 $x_u-x_v\ge k$,则对于每个 $i$,连 $(v,i)\to(u,i+k)$,流量为 $\inf$ 的边,这条边一定不会被断掉,这么做的意义是,如果 $(v,i)\to(v,i+1)$ 的边断掉,即 $x_v=i$,那么所有 $(u,j),j\le i+k$ 都应当被划入 $S$ 一侧的集合,所以链上被割的边应该在 $(u,i+k)$ 之后,即 $x_u\ge i+k$。

image-20230118192657435

类似的,如果 $x_u-x_v\le k$,对于每个 $i$,连 $(u,i+k)\to(v,i)$ 的边即可。

当然,这样连边仍然存在问题,如下是一个例子。

image-20230118193151108

很显然,该网络对应的模型无解,但我们却可以通过割掉 $1\to2,3\to4,5\to6,7\to8$ 四条边求出一个显然不符合预期的解。

接下来,针对该问题我们有两种不同的解决方案。

  • 在连接 $5\to3,6\to4$ 时,由于 $7,8$ 没有点可连,我们就将他们放弃了,但问题也出在这里,假如错误的将 $7$ 划入 $S$,而 $6$ 划入 $T$,$7$ 的限制就无法表达,所以我们可以连接 $7\to4,8\to4$ 确保所有限制都被准确描述。

    另一种理解方式是,这样确保了在任何无解的时候都存在从 $S\to T$,流量为 $\inf$ 的路径。

  • 出现问题的原因是我们在一条链上割了多于 1 条边,从而导致链上每个点被划分到的集合变的混乱,这种情况不应该出现。

    我们可以考虑连接 $(u,i+1)\to(u,i)$,流量为 $\inf$ 的反向边,这样确保了如果 $(u,i)$ 被划分到 $S$,所有 $(u,j),j<i$ 都被划分到 $S$,这样一条链上的点一定是前一部分属于 $S$,后一部分属于 $T$,从而也保证了只会割一条边。

    同样的,我们也可以理解成构造了一条 $S\to T$,流量为 $\inf$ 的路径。

具体的,回到本题,我们可以方便的计算出第 $i$ 位选手做第 $j$ 套题的期望得分为:
$$
\sum_{k=1}^pc_k\prod_{t=1}^kf_{i,j,t}
$$
然后按照上述模型建图求解即可。

Code

namespace Main{
    const int N = 10005;
    const int M = 1000005;
    const double inf = 1e10;
    const double eps = 1e-6;
    int t, n, m, p, y, S, T, c[N];
    int cnt, d[N], start[N];
    double val[M], a[100][100];
    bool vis[N];
    struct edge{
        int to, id;
        edge() {}
        edge(int to, int id): to(to), id(id) {}
    };
    vector<edge> g[N];
    void add(int u, int v, double w){
        // cerr << u << ' ' << v << ' ' << w << endl;
        cnt++;
        assert(cnt <= 500000);
        g[u].emplace_back(v, cnt<<1);
        g[v].emplace_back(u, cnt<<1|1);
        val[cnt<<1] = w;
        val[cnt<<1|1] = 0;
    }
    bool bfs(int s, int t){
        queue<int> q;
        memset(d, -1, sizeof(d));
        q.push(s);
        d[s] = 0;
        while(!q.empty()){
            int u = q.front(); q.pop();
            start[u] = 0;
            for(edge v: g[u]){
                if(val[v.id] > eps && d[v.to] == -1){
                    d[v.to] = d[u] + 1;
                    q.push(v.to);
                }
            }
        }
        return d[t] != -1;
    }
    double dfs(int u, double in){
        if(u == T) return in;
        double out = 0;
        for(int i = start[u]; i < g[u].size() && in - out > eps; i++){
            edge v = g[u][i];
            start[u] = i;
            if(val[v.id] > eps && d[v.to] == d[u] + 1){
                double res = dfs(v.to, min(val[v.id], in - out));
                if(res < eps) d[v.to] = -1;
                val[v.id] -= res;
                val[v.id^1] += res;
                out += res;
            }
        }
        return out;
    }
    double dinic(int s, int t){
        double res = 0;
        while(bfs(s, t)) res += dfs(s, inf);
        return res;
    }
    void Main(){
        input(t);
        while(t--){
            for(int i = 0; i <= T; i++) g[i].clear();
            cnt = 0;
            input(n, m, p, y);
            for(int i = 0; i < p; i++){
                input(c[i]);
                if(i) c[i] += c[i-1];
            }
            for(int i = 0; i < m; i++){
                for(int j = 0; j < n; j++){
                    a[j][i] = 0;
                    double mul = 1, tmp;
                    for(int k = 0; k < p; k++){
                        scanf("%lf", &tmp);
                        if(k) a[j][i] += mul * (1 - tmp) * c[k-1];
                        mul *= tmp;
                    }
                    a[j][i] += mul * c[p-1];
                }
            }
            auto id = [=](int x, int y){
                assert(x >= 0 && y >= 0 && x * (m + 1) + y < N);
                return x * (m + 1) + y;
            };
            S = n * (m + 1), T = S + 1;
            for(int i = 0; i < n; i++){
                add(S, id(i, 0), inf); add(id(i, m), T, inf);
                for(int j = 0; j < m; j++){
                    add(id(i, j), id(i, j + 1), a[i][j]);
                    add(id(i, j + 1), id(i, j), inf);
                }
            }
            for(int i = 0; i < y; i++){
                int u, v, w;
                input(u, v, w); v--, u--;
                for(int j = max(0, -w); j <= m && j + w <= m; j++) add(id(v, j), id(u, j + w), inf);
            }
            double ans = dinic(S, T);
            if(inf - ans > eps) printf("%.6lf", ans), puts("");
            else puts("-1");
        }
        return;
    }
} // namespace
Archives Tip
QR Code for this page
Tipping QR Code