传送门:Cyclic Tour
题意
在 n 个点、m 条有向边的图中,找到使得每个点属于且仅属于一个环的若干个环,求环的边权和的最小值。
思路
一个环如 1→2→3→1,若将每个点拆成两个(如 1 拆为 1 与 1’)就会变成 1→2’→2→3’→3→1’,把标号相同的点间的边去掉,就变成了 1→2’,2→3’,3→1’。可以看到,一个环很清楚地将拆后的点分为两个部分(带 ’ 与不带 ’),这就意味着,我们可以用二分图来解决这个问题,且容易发现这是个二分图带权匹配问题,用 KM 算法可解。
要注意的坑是,本题需要处理重边。
代码
#include <bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 1e2+2;
int n, nx, ny, m;
int g[maxn][maxn];
int linker[maxn], lx[maxn], ly[maxn];
int slack[maxn];
bool visx[maxn], visy[maxn];
void init()
{
for(int i = 0; i < maxn; ++i)
for(int j = 0; j < maxn; ++j)
g[i][j] = -INF;
}
void read()
{
nx = ny = n;
int u, v, w;
for(int i = 0; i < m; ++i)
{
cin >> u >> v >> w;
g[u-1][v-1] = max(g[u-1][v-1], -w);
}
}
bool dfs(int x)
{
visx[x] = 1;
for(int i = 0; i < ny; ++i)
{
if(visy[i])
continue;
int t = lx[x] + ly[i] - g[x][i];
if(!t)
{
visy[i] = 1;
if(linker[i] == -1 || dfs(linker[i]))
{
linker[i] = x;
return 1;
}
}
else
slack[i] = min(slack[i], t);
}
return 0;
}
int KM()
{
memset(linker, -1, sizeof(linker));
memset(ly, 0, sizeof(ly));
for(int i = 0; i < nx; ++i)
{
lx[i] = -INF;
for(int j = 0; j < ny; ++j)
lx[i] = max(lx[i], g[i][j]);
}
for(int i = 0; i < nx; ++i)
{
memset(slack, INF, sizeof(slack));
while(1)
{
memset(visx, 0, sizeof(visx));
memset(visy, 0, sizeof(visy));
if(dfs(i))
break;
int d = INF;
for(int j = 0; j < ny; ++j)
if(!visy[j])
d = min(d, slack[j]);
if(d == INF)
return -1;
for(int j = 0; j < nx; ++j)
if(visx[j])
lx[j] -= d;
for(int j = 0; j < ny; ++j)
{
if(visy[j])
ly[j] += d;
else
slack[j] -= d;
}
}
}
int ret = 0, cnt = 0;
for(int i = 0; i < ny; ++i)
{
if(~linker[i] && g[linker[i]][i] != -INF) //有匹配
{
ret += g[linker[i]][i]; //加上权值
++cnt; //记录经过的点的个数
}
}
if(cnt != nx) //如果不是所有点都在某个环内
return -1; //则不满足要求
return -ret;
}
void solve()
{
cout << KM() << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
while(cin >> n >> m)
{
init();
read();
solve();
}
return 0;
}