[BZOJ3996][TJOI2015]线性代数(网络最小割)

Address

https://www.lydsy.com/JudgeOnline/problem.php?id=3996

Solution

涨姿势了,认识最小割经典模型
矩阵乘法不好搞,但 A 是个 01 矩阵。
因此容易把矩阵乘法转化为另一个问题:
求一个集合 S { 1 , 2 , . . . , n } ,选择 i 作为集合元素有 C i 的代价。收益为 B 只保留行号和列号均为 S 的子矩阵的元素之和。
S = { 1 , 2 , 5 } ,则收益为 B ( 1 , 1 ) ( 1 , 2 ) ( 1 , 5 ) ( 2 , 1 ) ( 2 , 2 ) ( 2 , 5 ) ( 5 , 1 ) ( 5 , 2 ) ( 5 , 5 ) 位置上的元素之和。
求 总收益减总代价 的最大值。
换个思路:选择 i 作为集合元素有 C i 的代价,不作为集合元素则产生的代价为:

j = i k = i B [ j , k ]

但如果同样的 B [ j , k ] 被多个 i 影响到,则只计算一次贡献。
求最小代价和。
容易得到最终要输出的答案是 B 矩阵中所有数之和减去最小代价和。
这是一个对每个 i 做出选择的问题,可以用最小割解决。
建立源点和汇点 , n ( n + 1 ) 2 个点 ( i , j ) i j ), n 个点 i
(1)由源点向所有的 ( i , j ) 建边。如果 i = j 则容量 B [ i , j ] ,否则 B [ i , j ] + B [ j , i ]
(2)由 ( i , j ) i j 建边(如果 i = j 则只建一条),容量
(3)由 i 向汇点建边,容量 C i
简单解析一下:
(1)割掉 i 到汇的边,就能使 i 不能到达汇。就相当于选 i 作为集合元素。
(2)如果不割掉 i 到汇的边,就必须切断源到 i 的路径。而所有的 ( i , j ) ( j , i ) i 的边是割不掉的(因为权值 ),所以只能割掉从源到所有 ( i , j ) ( j , i ) 的边。这些边就对应了 B 矩阵中行号或列号为 i 的格子。

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Edge(u) for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
#define NF(u) for (int &e = cur[u], v = go[e]; e; e = nxt[e], v = go[e])
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 505, M = 1e6 + 5, INF = 0x3f3f3f3f;
int n, b[N][N], c[N], ecnt = 1, nxt[M], adj[M], go[M], cap[M],
lev[M], cur[M], len, que[M], S, T, sum;
void add_edge(int u, int v, int w) {
    nxt[++ecnt] = adj[u]; adj[u] = ecnt;
    go[ecnt] = v; cap[ecnt] = w;
    nxt[++ecnt] = adj[v]; adj[v] = ecnt;
    go[ecnt] = u; cap[ecnt] = 0;
}
bool bfs() {
    int i;
    For (i, S, T) lev[i] = -1, cur[i] = adj[i];
    lev[que[len = 1] = S] = 0;
    For (i, 1, len) {
        int u = que[i];
        Edge(u)
            if (cap[e] && lev[v] == -1) {
                lev[que[++len] = v] = lev[u] + 1;
                if (v == T) return 1;
            }
    }
    return 0;
}
int dinic(int u, int flow) {
    if (u == T) return flow;
    int res = 0, delta;
    NF(u) if (cap[e] && lev[u] < lev[v]) {
        delta = dinic(v, min(cap[e], flow - res));
        if (delta) {
            cap[e] -= delta; cap[e ^ 1] += delta;
            res += delta; if (res == flow) break;
        }
    }
    if (res != flow) lev[u] = -1;
    return res;
}
int solve() {
    int res = 0;
    while (bfs()) res += dinic(S, INF);
    return res;
}
int main() {
    int i, j, tot = 1;
    n = read();
    For (i, 1, n) For (j, 1, n) sum += (b[i][j] = read());
    For (i, 1, n) c[i] = read();
    S = 1; T = (n * (n + 1) >> 1) + n + 2;
    For (i, 1, n) For (j, i, n) {
        tot++;
        add_edge(S, tot, i == j ? b[i][j] : b[i][j] + b[j][i]);
        add_edge(tot, 1 + (n * (n + 1) >> 1) + i, INF);
        if (i != j) add_edge(tot, 1 + (n * (n + 1) >> 1) + j, INF);
    }
    For (i, 1, n) add_edge(1 + (n * (n + 1) >> 1) + i, T, c[i]);
    cout << sum - solve() << endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/xyz32768/article/details/81458804
今日推荐