\(\text{Solution}\)
模板题,二分图带权匹配
第一问费用流即可
第二问考虑枚举最优方案的一条边,删掉,再跑费用流,不能跑出之前的结果说明此边为必经边
\(\text{Code}\)
#include <cstdio>
#include <queue>
#define RE register
#define IN inline
using namespace std;
const int N = 165, INF = 5001;
int n, m, S, T, ansflow, anscost;
int pre[N], edge[N], dis[N], flow[N], vis[N], w[N][N], g[N][N], a[N][N], b[N][N], c[N][N];
queue<int> Q;
IN int spfa()
{
for(RE int i = S; i <= T; i++) dis[i] = -INF, flow[i] = INF, vis[i] = 0;
dis[S] = 0, vis[S] = 1, Q.push(S), pre[T] = -1;
while (!Q.empty())
{
int now = Q.front(); Q.pop();
for(RE int i = S; i <= T; i++)
if (w[now][i] && dis[now] + b[now][i] > dis[i])
{
dis[i] = dis[now] + b[now][i], pre[i] = now, flow[i] = min(flow[now], w[now][i]);
if (!vis[i]) vis[i] = 1, Q.push(i);
}
vis[now] = 0;
}
return pre[T] != -1;
}
IN int MCMF(int ty)
{
int Maxflow = 0, Mincost = 0;
while (spfa())
{
Maxflow += flow[T], Mincost += dis[T] * flow[T];
int now = T;
while (now != S) w[pre[now]][now] -= flow[T], w[now][pre[now]] += flow[T], now = pre[now];
if (Maxflow == n) break;
}
if (ty) ansflow = Maxflow, anscost = Mincost;
else return ((Maxflow == ansflow) && (Mincost == anscost));
}
int main()
{
freopen("match.in", "r", stdin), freopen("match.out", "w", stdout);
scanf("%d", &n), S = 0, T = n + n + 1;
for(RE int i = 1; i <= n; i++)
for(RE int j = 1; j <= n; j++)
scanf("%d", &a[i][j + n]), a[j + n][i] = -a[i][j + n], g[i][j + n] = 1;
for(RE int i = 1; i <= n; i++) g[S][i] = 1, g[i + n][T] = 1;
for(RE int k = S; k <= T; k++)
for(RE int l = S; l <= T; l++) w[k][l] = g[k][l], b[k][l] = a[k][l];
MCMF(1), printf("%d\n", anscost);
for(RE int k = S; k <= T; k++)
for(RE int l = S; l <= T; l++) c[k][l] = w[k][l];
for(RE int i = 1; i <= n; i++)
for(RE int j = 1; j <= n; j++)
if (!c[i][j + n])
{
for(RE int k = S; k <= T; k++)
for(RE int l = S; l <= T; l++) w[k][l] = g[k][l], b[k][l] = a[k][l];
w[i][j + n] = 0, b[i][j + n] = -INF;
if (!MCMF(0)) printf("%d %d\n", i, j);
}
}