Address
https://www.lydsy.com/JudgeOnline/problem.php?id=3996
Solution
涨姿势了,认识最小割经典模型
矩阵乘法不好搞,但
是个
矩阵。
因此容易把矩阵乘法转化为另一个问题:
求一个集合
,选择
作为集合元素有
的代价。收益为
只保留行号和列号均为
的子矩阵的元素之和。
如
,则收益为
的
位置上的元素之和。
求 总收益减总代价 的最大值。
换个思路:选择
作为集合元素有
的代价,不作为集合元素则产生的代价为:
但如果同样的 被多个 影响到,则只计算一次贡献。
求最小代价和。
容易得到最终要输出的答案是 矩阵中所有数之和减去最小代价和。
这是一个对每个 做出选择的问题,可以用最小割解决。
建立源点和汇点 , 个点 ( ), 个点 。
(1)由源点向所有的 建边。如果 则容量 ,否则 。
(2)由 向 和 建边(如果 则只建一条),容量 。
(3)由 向汇点建边,容量 。
简单解析一下:
(1)割掉 到汇的边,就能使 不能到达汇。就相当于选 作为集合元素。
(2)如果不割掉 到汇的边,就必须切断源到 的路径。而所有的 或 到 的边是割不掉的(因为权值 ),所以只能割掉从源到所有 或 的边。这些边就对应了 矩阵中行号或列号为 的格子。
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;
}