Solution Set -「网络流」 1

cirnovsky /

「codeforces - 1592F2」Alice and Recoloring 2:妙妙题,显然只会操作 (1,1)(1, 1)(n,m)(n, m),分别记作操作 1 和操作 2。我们希望单点操作,所以考虑差分(trick),只是这个题的差分非常高妙。如果你直接对前缀做二维差分会发现操作 1 要修改四个点,操作 2 修改一个点,而操作 1 花费 11,2 花费 22,所以每个点可能被操作最多两次(考虑一种情况,即 (1,1)(1, 1)(1,2)(1, 2)(2,1)(2, 1) 是黑点,(2,2)(2, 2) 是白点)。xf 教我了一种高庙的差分,具体是 ci,j=ai,jai+1,jai,j+1ai+1,j+1c_{i, j} = a_{i,j} \oplus a_{i+1, j} \oplus a_{i, j+1} \oplus a_{i+1, j+1},这样差分操作 2 就修改四个点,操作 1 修改一个点,这样一个点就最多修改一次了,建出二分图即可。

最后的做法是,假设我全部用操作 1,然后反悔看最多能用多少次操作 2(因为会用在 (i,j)(i, j) 上用操作 2 的情况是 (i,j)(i, j)(n,j)(n, j)(i,m)(i, m) 都是黑点)。

最后 (n,m)(n, m) 是需要特判的。

各种意义上我不会的题,膜拜 xf。

char s[600];
int n, m, a[600][600];
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >> n >> m;
    for (int i=1; i<=n; ++i) {
        cin >> s;
        for (int j=1; j<=m; ++j) {
            a[i][j] = s[j-1] == 'B';
        }
    }
    for (int i=1; i<=n; ++i) {
        for (int j=1; j<=m; ++j) {
            a[i][j] ^= a[i+1][j]^a[i][j+1]^a[i+1][j+1];
        }
    }
    mf_graph<int> g(n+m+2);
    const int src = n+m+1, ter = n+m+2;
    for (int i=1; i<n; ++i) {
        for (int j=1; j<m; ++j) {
            if (a[i][j] && a[n][j] && a[i][m]) {
                g.add_edge(i, j+n, 1);
            }
        }
    }
    for (int i=1; i<=n; ++i) g.add_edge(src, i, 1);
    for (int i=1; i<=m; ++i) g.add_edge(i+n, ter, 1);
    int ret = -g.flow(src, ter);
    a[n][m] ^= (-ret)&1;
    for (int i=1; i<=n; ++i) {
        for (int j=1; j<=m; ++j) ret += a[i][j];
    }
    cout << ret << "\n";
}

「codeforces - 1146G」Zoning Restrictions:还行的题。想到把每个点拆成 h+1h+1 个点,如果你定义状态 (i,j)(i, j) 表示第 ii 个点的高度为 jj 会发现并不优秀,改成高度大于等于 jj 会好一点。关于这题怎么想到 min-cut 的,可以这样考虑(which is also a common trick):全局最理想的答案是 h2×nh^2 \times n,然而不一定合法,我们以最小的代价去把这个答案调整到合法,得到的一定是最优的答案,因此是 min-cut。然而这道题我们不会这样去想,这只是一些题的思路。对于这个题我们的想法是把所有的贡献取负,这样就成了最小值()

这里涉及另一个思维上的 trick:最小割的源汇有什么意义?划分出的两个点集有什么意义?在这一题中我们不妨将点集 SS 中的事件称为「成立」,TT 中的事件称为「不成立」,这样就赋予了最小割意义。

这里给出构造网络的方案:(i,j),(i,j+1),j2\lang(i, j), (i, j+1), -j^2\rang(j,0),xi+1,,j[li,ri]\lang (j, 0), x_i+1, \infty \rang, \forall j \in [l_i, r_i]i,t,ci\lang i, t, c_i \rang

考察意义:割掉第一类边的意义即令房子 ii 的高度为 jj,这对答案的贡献为 j2j^2,我们对答案取了负,所以贡献为 j2-j^2,如果第二类边有流量则意味着付罚金,因为一个区间只需要付一次罚金,所以我们不能直接割掉第二类边,而是通过第二类边使得割掉第三类边有意义(断掉连通性)。

当然流量不可能是负的,所以选择给 j2-j^2 加上 h2h^2,因为 nn 幢房子代表的每一条链必然割且仅割一条一类边,所以最后给答案加上 h2×nh^2 \times n 即可。

const int inf = 0x3f3f3f3f;
int n, h, m, id[60][60], qwq;
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >> n >> h >> m;
    for (int i=1; i<=n; ++i) {
        for (int j=0; j<=h+1; ++j) id[i][j] = ++qwq;
    }
    mf_graph<int> g(qwq+m+2);
    const int src = qwq+m+1, ter = qwq+m+2;
    for (int i=1; i<=n; ++i) {
        g.add_edge(src, id[i][0], inf);
    }
    for (int i=1; i<=n; ++i) {
        for (int j=0; j<=h; ++j) g.add_edge(id[i][j], id[i][j+1], h*h-j*j);
    }
    for (int i=1,l,r,x,c; i<=m; ++i) {
        cin >> l >> r >> x >> c;
        g.add_edge(i+qwq, ter, c);
        for (int j=l; j<=r; ++j) {
            g.add_edge(id[j][x+1], i+qwq, inf);
        }
    }
    cout << h*h*n-g.flow(src, ter) << "\n";
}

「codeforces - 1061E」Politics:第一步是想到把 tree 1 放左部 tree 2 放右部。然后进入这个题的 key step:将对于子树的限制(被钦定的结点个数)转化为对一个结点的限制。这里大部分的做法是:对于一个有限制的结点 xx,将 kxk_x 减去 ysubtree(x),yxky\displaystyle \sum_{y \in \text{subtree}(x), y \neq x} k_y,从而我们的工作就变成了在「所有 xx 的子树中的有限制的结点的上方中钦定一些结点」。于是给出这题的建图(费用流):s,xV1,kxysubtree(x),yxky,0\displaystyle \lang s, x \in\textbf V_1,k_x-\sum_{y \in \text{subtree}(x), y \neq x} k_y, 0\rangxV2,t,kxysubtree(x),yxky,0\displaystyle \lang x \in\textbf V_2, t,k_x-\sum_{y \in \text{subtree}(x), y \neq x} k_y, 0\rangupi,upi,1,ai\lang up_i, up'_i, 1, a_i\rang,其中 v_1 v_2 是两棵树的点集,up_i 是编号 ii 在 tree 1 中对应的结点的最近有限制祖先,up'_i 同理。

理解一下,一、二类边都对应每个结点的限制,第三类边就是反过来考虑(我们之前是对祖先 xx 考虑,要在子树中所有有限制的结点的上面钦定结点),现在我们对一个点 xx 考虑它对最近有限制祖先的贡献。如果钦定了编号 ii,则会对答案造成 aia_i 的贡献,占据 up_i 和 up'_i 的空余可钦定数。

mcf_graph<int, int> g;
int src, ter, qwq, qaq;
int n, rtx, rty, q1, q2, a[600], bstx[600], bsty[600], upx[600], upy[600], parx[600], pary[600];
bsi<int> adjx[600], adjy[600];
int dfsx(int x, int pa, int t) {
    parx[x] = pa;
    if (bstx[x]) {
        t = x;
    }
    upx[x] = t;
    int sum = 0;
    for (auto y : adjx[x]) {
        if (y != pa) {
            sum += dfsx(y, x, t);
        }
    }
    if (bstx[x]) {
        if (bstx[x] < sum) fucked_up();
        qwq += bstx[x]-sum;
        g.add_edge(src, x, bstx[x]-sum, 0);
        return bstx[x];
    }
    return sum;
}
int dfsy(int x, int pa, int t) {
    pary[x] = pa;
    if (bsty[x]) {
        t = x;
    }
    upy[x] = t;
    int sum = 0;
    for (auto y : adjy[x]) {
        if (y != pa) {
            sum += dfsy(y, x, t);
        }
    }
    if (bsty[x]) {
        if (bsty[x] < sum) fucked_up();
        qaq += bsty[x]-sum;
        g.add_edge(x+n, ter, bsty[x]-sum, 0);
        return bsty[x];
    }
    return sum;
}
int getx(int x, int pa) {
    int res = 0;
    for (auto y : adjx[x]) {
        if (y != pa) res += getx(y, x);
    }
    if (bstx[x]) {
        return bstx[x];
    }
    return res;
}
int gety(int x, int pa) {
    int res = 0;
    for (auto y : adjy[x]) {
        if (y != pa) res += gety(y, x);
    }
    if (bsty[x]) {
        return bsty[x];
    }
    return res;
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >> n >> rtx >> rty;
    for (int i=1; i<=n; ++i) {
        cin >> a[i];
    }
    for (int i=1,x,y; i<n; ++i) {
        cin >> x >> y;
        adjx[x] += y, adjx[y] += x;
    }
    for (int i=1,x,y; i<n; ++i) {
        cin >> x >> y;
        adjy[x] += y, adjy[y] += x;
    }
    cin >> q1;
    for (int i=0,x; i<q1; ++i) {
        cin >> x >> bstx[x];
    }
    cin >> q2;
    for (int i=0,x; i<q2; ++i) {
        cin >> x >> bsty[x];
    }
    g = mcf_graph<int, int>(n*2+2);
    src = n*2+1, ter = n*2+2;
    dfsx(rtx, 0, 0), dfsy(rty, 0, 0);
    if (qwq != qaq) fucked_up();
    for (int i=1; i<=n; ++i) {
        if (upx[i] && upy[i]) {
            g.add_edge(upx[i], upy[i]+n, 1, -a[i]);
        }
    }
    auto [X, Y] = g.flow(src, ter);
    if (X != qwq) fucked_up();
    cout << -Y << "\n";
}