[HDU1853] Cyclic Tour(拆点 + KM)

 传送门: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;
}

猜你喜欢

转载自blog.csdn.net/HNUCSEE_LJK/article/details/101288385