kuangbin专题十匹配问题总结

总结:该专题主要分为4个内容,二分匹配,二分多重匹配,二分图最大权匹配,一般图匹配。
先说第一个部分的内容。
匈牙利算法模板(时间复杂度O(V*E)):
D - 棋盘游戏
首先我们可以根据行和列建一个二分图,然后跑最大匹配得到一个ans。我们现在要求重要点的个数,也就可以枚举已经匹配好的那些点,把其中的格子变成不能放的格子,然后再跑最大匹配,看得到的结果是否和ans一样,如果一样那么它就不是重要点了。这样的做法也有可以优化的地方。我们可以想一想,求最大匹配的过程其实就是不断的找增广路的过程,我们现在删掉其中的一条边,重点是想要知道还没匹配好的点是否能找到增广路。所以也就没有必要重新跑最大匹配,而是直接找那些没匹配好的点的增广路就行了。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxn = 105;
const int maxm = 1005;
bool map[maxn][maxn];
int N, M, K;
bool link[maxm][maxm];
bool used[maxm];
int girl[maxm];
int boy[maxm];
bool flag;
bool find(int u)
{
    for (int i = 1;i <= M;i++)if (link[u][i] && !used[i])
    {
        used[i] = 1;
        if (!girl[i] || find(girl[i]))
        {
            if (flag)
            {
                girl[i] = u;
                boy[u] = i;
            }
            return 1;
        }
    }
    return 0;
}

int hungary()
{
    int ans = 0;
    flag = true;
    for (int i = 1;i <= N;i++)
    {
        memset(used, 0, sizeof(used));
        if (find(i))ans++;
    }
    return ans;
}

bool check()
{
    for (int i = 1;i <= N;i++)if (!boy[i])
    {
        memset(used, 0, sizeof(used));
        if (find(i))
            return 0;
    }
    return 1;
}

int solve(int num)
{
    int ans = 0;
    flag = false;
    for (int i = 1;i <= M;i++)if(girl[i])
    {
        int u =girl[i];
        link[u][i] = 0;
        girl[i] = 0;
        boy[u] = 0;
        if (check())ans++;
        girl[i] = u;
        link[u][i] = 1;
        boy[u] = i;
    }
    return ans;
}

void init()
{
    memset(map, 0, sizeof(map));
    memset(link, 0, sizeof(link));
    memset(girl, 0, sizeof(girl));

}

int main()
{
    int cas = 1;
    while (~scanf("%d %d %d",&N,&M,&K))
    {
        init();
        int x, y;
        for (int i = 1;i <= K;i++)
        {
            scanf("%d %d", &x, &y);
            map[x][y] = 1;
            link[x][y] = 1;
        }
        int ans = hungary();

        printf("Board %d have %d important blanks for %d chessmen.\n",cas++,solve(ans),ans);
    }
    return 0;
}

F - Rain on your Parade
我们知道匈牙利算法的复杂度O(V*E),这题如果边数很多的话明显会T。所以这里需要另一种算法Hopcroft-Carp算法(时间复杂度O( VE V E )).

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<stdio.h>
using namespace std;
const int maxn = 3005;
const int INF = 0x3f3f3f3f;

struct node
{
    int x, y, s;
    void input1()
    {
        scanf("%d %d %d", &x, &y, &s);
    }
    void input2()
    {
        scanf("%d %d", &x, &y);
    }

}p1[maxn], p2[maxn];
int t, m, n;
bool link[maxn][maxn];
int dist(node a, node b)
{
    int x = a.x - b.x;
    int y = a.y - b.y;
    return x*x + y*y;
}

void buildmap()
{
    for (int i = 1;i <= m;i++)
        for (int j = 1;j <= n;j++)
            if (p1[i].s*t*p1[i].s*t >= dist(p1[i], p2[j]))
                link[i][j] = 1;
}

int cx[maxn], cy[maxn];
int dx[maxn], dy[maxn];
bool used[maxn];
int dis;
bool bfs()
{
    queue<int>Q;
    dis = INF;
    memset(dx, -1, sizeof(dx));
    memset(dy, -1, sizeof(dy));
    for (int i = 1;i <= m;i++)if (cx[i] == -1)
        dx[i] = 0, Q.push(i);
    while (!Q.empty())
    {
        int u = Q.front();Q.pop();
        if (dx[u] > dis)break;
        for (int i = 1;i <= n;i++)if(link[u][i]&&dy[i]==-1)
        {
            dy[i] = dx[u] + 1;
            if (cy[i] == -1)dis = dy[i];
            else
            {
                dx[cy[i]] = dy[i] + 1;
                Q.push(cy[i]);
            }
        }
    }
    return dis != INF;
}

int find(int u)
{
    for (int i = 1;i <= n;i++)if (link[u][i]&&!used[i]&&dy[i]==dx[u]+1)
    {
        used[i] = 1;
        if (cy[i] != -1 && dy[i]==dis)continue;
        if (cy[i] == -1 || find(cy[i]))
        {
            cx[u] = i;
            cy[i] = u;
            return 1;
        }
    }
    return 0;
}

int MaxMatch()
{
    memset(cx, -1, sizeof(cx));
    memset(cy, -1, sizeof(cy));
    int res = 0;
    while (bfs())
    {
        memset(used, 0, sizeof(used));
        for (int i = 1;i <= m;i++)
        {
            if (cx[i] == -1)
                res += find(i);
        }
    }
    return res;
}

void init()
{
    memset(link, 0, sizeof(link));
}

int main()
{
    int T;
    scanf("%d", &T);
    int cas = 1;
    while (T--)
    {
        init();
        scanf("%d %d", &t,&m);
        for (int i = 1;i <= m;i++)
            p1[i].input1();

        scanf("%d", &n);
        for (int i = 1;i <= n;i++)
            p2[i].input2();
        buildmap();
        printf("Scenario #%d:\n", cas++);
        printf("%d\n\n", MaxMatch());
    }
    return 0;


}

G - Oil Skimming
这题建图十分巧妙。。反正我没想出来。。图上的每个顶点都有横坐标和纵坐标,设横坐标和纵坐标和为sum我们选择覆盖肯定一个点的sum为奇数,一个为偶数。所以我们可以根据这个来建一个二分图。

#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int maxn = 605;
const int maxm = 1805;
const int inf = 0x3f3f3f3f;
struct Edge
{
    int u, v, next;
    Edge(int _u,int _v,int _nxt):u(_u),v(_v),next(_nxt){}
    Edge(){}
}edges[maxm * 4];
int head[maxm], tot;
void addedge(int u, int v)
{
    edges[tot] = Edge(u, v, head[u]);
    head[u] = tot++;
}

char map[605][605];


int nL, nR;
int Map[605][605];
int N;
int dir[4][2] = { -1,0,1,0,0,-1,0,1 };
void buildmap()
{
    nL = 0, nR = 0;
    for(int i=1;i<=N;i++)
        for (int j = 1;j <= N;j++)if(map[i][j]=='#')
        {
            if ((i + j) % 2)
                nL++, Map[i][j] = nL;
            else
                nR++, Map[i][j] = nR;
        }
    for(int i=1;i<=N;i++)
        for (int j = 1;j <= N;j++)if (map[i][j] == '#' && (i + j) % 2)
        {
            for (int k = 0;k < 4;k++)
            {
                int tmpx = i + dir[k][0], tmpy = j + dir[k][1];
                if (Map[tmpx][tmpy])
                    addedge(Map[i][j], Map[tmpx][tmpy]);
            }
        }
}

int cx[maxm], cy[maxm];
int dx[maxm], dy[maxm];
bool used[maxm];
int dis;
bool bfs()
{
    dis = inf;
    queue<int>Q;
    memset(dx, -1, sizeof(dx));
    memset(dy, -1, sizeof(dy));
    for (int i = 1;i <= nL;i++)if (cx[i] == -1)
        Q.push(i), dx[i] = 0;
    while (!Q.empty())
    {
        int u = Q.front();Q.pop();
        if (dx[u] > dis)break;
        for (int i = head[u];~i;i = edges[i].next)
        {
            int v = edges[i].v;
            if (dy[v] == -1)
            {
                dy[v] = dx[u] + 1;
                if (cy[v] == -1)dis = dy[v];
                else
                {
                    dx[cy[v]] = dy[v] + 1;
                    Q.push(cy[v]);
                }
            }
        }
    }
    return dis != inf;

}

int find(int u)
{
    for (int i = head[u];~i;i = edges[i].next)
    {
        int v = edges[i].v;
        if (!used[v] && dy[v] == dx[u] + 1)
        {
            used[v] = 1;
            if (cy[v] != -1 && dy[v] == dis)continue;
            else if (cy[v] == -1 || find(cy[v]))
            {
                cx[u] = v;
                cy[v] = u;
                return 1;
            }
        }
    }
    return 0;
}

int MaxMatch()
{
    int res = 0;
    memset(cx, -1, sizeof(cx));
    memset(cy, -1, sizeof(cy));
    while (bfs())
    {
        memset(used, 0, sizeof(used));
        for (int i = 1;i <= nL;i++)
        {
            if (cx[i] == -1)
                res += find(i);
        }
    }

    return res;
}

void init()
{
    tot = 0;
    memset(head, -1, sizeof(head));
    memset(Map, 0, sizeof(Map));
}
int main()
{
    int K;
    scanf("%d", &K);
    int cas = 1;
    while (K--)
    {
        init();

        scanf("%d", &N);
        for (int i = 1;i <= N;i++)
            scanf("%s", map[i] + 1);
        buildmap();
        printf("Case %d: %d\n", cas++, MaxMatch());
    }
    return 0;

}

接下来是关于二分图的一些结论题。
注意:这些结论只针对二分图来讲,如果是一般图是不成立的。
关于结论的证明博主会在另一篇博客详细的讲解。
I - Strategic Game
题意:选择最少的点把所有的边都覆盖上(最小顶点覆盖问题)。
最小顶点覆盖数=二分图的最大匹配数。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
#include<queue>
using namespace std;
const int maxn = 1505;
const int inf = 0x3f3f3f3f;
bool link[maxn][maxn];
bool used[maxn];
int cx[maxn], cy[maxn];
int dx[maxn], dy[maxn];
int dis;
int n;

bool bfs()
{
    memset(dx, -1, sizeof(dx));
    memset(dy, -1, sizeof(dy));
    dis = inf;
    queue<int>Q;
    for (int i = 0;i < n;i++)if (cx[i] == -1)
    {
        Q.push(i);
        dx[i] = 0;
    }
    while (!Q.empty())
    {
        int u = Q.front();Q.pop();
        if (dx[u] > dis)break;
        for (int i = 0;i < n;i++)if (link[u][i] && dy[i] == -1)
        {
            dy[i] = dx[u] + 1;
            if (cy[i] == -1)dis = dy[i];
            else
            {
                dx[cy[i]] = dy[i] + 1;
                Q.push(cy[i]);
            }
        }
    }
    return dis != inf;
}

int find(int u)
{
    for (int i = 0;i < n;i++)if (link[u][i] && !used[i] && dy[i] == dx[u] + 1)
    {
        used[i] = 1;
        if (cy[i] != -1 && dy[i] == dis)continue;
        else if (cy[i] == -1 || find(cy[i]))
        {
            cy[i] = u;
            cx[u] = i;
            return 1;
        }
    }
    return 0;
}

int MaxMatch()
{
    int res = 0;
    memset(cx, -1, sizeof(cx));
    memset(cy, -1, sizeof(cy));
    while (bfs())
    {
        memset(used, 0, sizeof(used));
        for (int i = 0;i < n;i++)
        {
            if (cx[i] == -1)
                res += find(i);
        }
    }
    return res;

}

void init()
{
    memset(link, 0, sizeof(link));
}

int main()
{

    while (~scanf("%d",&n))
    {
        init();
        int u, num,v;
        for (int i = 1;i <= n;i++)
        {
            scanf("%d:(%d)", &u, &num);
            for (int i = 1;i <= num;i++)
            {
                scanf("%d", &v);
                link[u][v] = 1;
                link[v][u] = 1;
            }
        }
        int ans = MaxMatch() / 2;
        printf("%d\n", ans);
    }
    return 0;
}

J - Air Raid
DAG最小不相交路径覆盖问题。
把一个点V拆成Vx和Vy,如果有边从A到B,那么Ax连向By。建完图后跑最大匹配,那么ans=图的顶点数-最大匹配数。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
#include<queue>
using namespace std;
const int maxn = 150;
const int inf = 0x3f3f3f3f;
bool link[maxn][maxn];
bool used[maxn];
int cx[maxn], cy[maxn];
int dx[maxn], dy[maxn];
int dis;
int n,m;

bool bfs()
{
    memset(dx, -1, sizeof(dx));
    memset(dy, -1, sizeof(dy));
    dis = inf;
    queue<int>Q;
    for (int i = 1;i <= n;i++)if (cx[i] == -1)
    {
        Q.push(i);
        dx[i] = 0;
    }
    while (!Q.empty())
    {
        int u = Q.front();Q.pop();
        if (dx[u] > dis)break;
        for (int i = 1;i <= n;i++)if (link[u][i] && dy[i] == -1)
        {
            dy[i] = dx[u] + 1;
            if (cy[i] == -1)dis = dy[i];
            else
            {
                dx[cy[i]] = dy[i] + 1;
                Q.push(cy[i]);
            }
        }
    }
    return dis != inf;
}

int find(int u)
{
    for (int i = 1;i <= n;i++)if (link[u][i] && !used[i] && dy[i] == dx[u] + 1)
    {
        used[i] = 1;
        if (cy[i] != -1 && dy[i] == dis)continue;
        else if (cy[i] == -1 || find(cy[i]))
        {
            cy[i] = u;
            cx[u] = i;
            return 1;
        }
    }
    return 0;
}

int MaxMatch()
{
    int res = 0;
    memset(cx, -1, sizeof(cx));
    memset(cy, -1, sizeof(cy));
    while (bfs())
    {
        memset(used, 0, sizeof(used));
        for (int i = 1;i <=n;i++)
        {
            if (cx[i] == -1)
                res += find(i);
        }
    }
    return res;

}

void init()
{
    memset(link, 0, sizeof(link));
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T--)
    {
        scanf("%d %d", &n, &m);
        init();
        int u, v;
        for (int i = 1;i <= m;i++)
        {
            scanf("%d %d", &u, &v);
            link[u][v] = 1;
        }
        int ans = MaxMatch();
        printf("%d\n", n - ans);
    }
}

K - Treasure Exploration
DAG最小相交路径覆盖问题。
做法:首先跑floyd求原图传递闭包,然后将问题转换成上述问题。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
#include<queue>
using namespace std;
const int maxn = 550;
const int inf = 0x3f3f3f3f;
bool link[maxn][maxn];
bool used[maxn];
int cx[maxn], cy[maxn];
int dx[maxn], dy[maxn];
int dis;
int n,m;

bool bfs()
{
    memset(dx, -1, sizeof(dx));
    memset(dy, -1, sizeof(dy));
    dis = inf;
    queue<int>Q;
    for (int i = 1;i <= n;i++)if (cx[i] == -1)
    {
        Q.push(i);
        dx[i] = 0;
    }
    while (!Q.empty())
    {
        int u = Q.front();Q.pop();
        if (dx[u] > dis)break;
        for (int i = 1;i <= n;i++)if (link[u][i] && dy[i] == -1)
        {
            dy[i] = dx[u] + 1;
            if (cy[i] == -1)dis = dy[i];
            else
            {
                dx[cy[i]] = dy[i] + 1;
                Q.push(cy[i]);
            }
        }
    }
    return dis != inf;
}

int find(int u)
{
    for (int i = 1;i <= n;i++)if (link[u][i] && !used[i] && dy[i] == dx[u] + 1)
    {
        used[i] = 1;
        if (cy[i] != -1 && dy[i] == dis)continue;
        else if (cy[i] == -1 || find(cy[i]))
        {
            cy[i] = u;
            cx[u] = i;
            return 1;
        }
    }
    return 0;
}

int MaxMatch()
{
    int res = 0;
    memset(cx, -1, sizeof(cx));
    memset(cy, -1, sizeof(cy));
    while (bfs())
    {
        memset(used, 0, sizeof(used));
        for (int i = 1;i <=n;i++)
        {
            if (cx[i] == -1)
                res += find(i);
        }
    }
    return res;

}
int d[maxn][maxn];
void init()
{
    memset(link, 0, sizeof(link));
    memset(d, inf, sizeof(d));
}


void floyd()
{

    for (int k = 1;k <= n;k++)
        for (int i = 1;i <= n;i++)
            for (int j = 1;j <= n;j++)
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
    for (int i = 1;i <= n;i++)
        for (int j = 1;j <= n;j++)
            if (d[i][j] != inf)
                link[i][j] = 1;
}

int main()
{
    while (~scanf("%d %d",&n,&m)&&n)
    {
        init();
        int u, v;
        for (int i = 1;i <= m;i++)
        {
            scanf("%d %d", &u, &v);
            d[u][v] = 1;
        }
        floyd();
        int ans = MaxMatch();
        printf("%d\n", n - ans);
    }
    return 0;
}

L - Cat VS Dog
最大独立集。
做法:将矛盾的两个孩子建边,然后求最大独立集即可。
最大独立集=点的个数-最大匹配。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
#include<queue>
using namespace std;
const int maxn = 550;
const int inf = 0x3f3f3f3f;
const int maxp = 505;
bool link[maxn][maxn];
bool used[maxn];
int cx[maxn], cy[maxn];
int dx[maxn], dy[maxn];
int dis;
int n, m, p;

bool bfs()
{
    memset(dx, -1, sizeof(dx));
    memset(dy, -1, sizeof(dy));
    dis = inf;
    queue<int>Q;
    for (int i = 1;i <= p;i++)if (cx[i] == -1)
    {
        Q.push(i);
        dx[i] = 0;
    }
    while (!Q.empty())
    {
        int u = Q.front();Q.pop();
        if (dx[u] > dis)break;
        for (int i = 1;i <= p;i++)if (link[u][i] && dy[i] == -1)
        {
            dy[i] = dx[u] + 1;
            if (cy[i] == -1)dis = dy[i];
            else
            {
                dx[cy[i]] = dy[i] + 1;
                Q.push(cy[i]);
            }
        }
    }
    return dis != inf;
}

int find(int u)
{
    for (int i = 1;i <= p;i++)if (link[u][i] && !used[i] && dy[i] == dx[u] + 1)
    {
        used[i] = 1;
        if (cy[i] != -1 && dy[i] == dis)continue;
        else if (cy[i] == -1 || find(cy[i]))
        {
            cy[i] = u;
            cx[u] = i;
            return 1;
        }
    }
    return 0;
}

int MaxMatch()
{
    int res = 0;
    memset(cx, -1, sizeof(cx));
    memset(cy, -1, sizeof(cy));
    while (bfs())
    {
        memset(used, 0, sizeof(used));
        for (int i = 1;i <= p;i++)
        {
            if (cx[i] == -1)
                res += find(i);
        }
    }
    return res;

}
void init()
{
    memset(link, 0, sizeof(link));
}
char s[maxp][2][100];

int main()
{
    while (~scanf("%d %d %d", &n, &m, &p))
    {
        init();
        for (int i = 1;i <= p;i++)
        {
            scanf("%s %s", s[i][0], s[i][1]);
        }

        for (int i = 1;i <= p;i++)
            for (int j = i + 1;j <= p;j++)
            {
                if (!strcmp(s[i][0], s[j][1]) || !strcmp(s[i][1], s[j][0]))
                    link[i][j] = 1, link[j][i] = 1;
            }
        int ans = MaxMatch()/2;
        ans = p - ans;
        printf("%d\n", ans);
    }
}

本专题的二分图的多重匹配没啥好说的,就是建图跑最大流就行了。贴一个模板题吧。

N - Optimal Milking

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
#include<vector>
#include<queue>
using namespace std;
const int maxn = 250;
const int maxv = 250;
const int inf = 0x3f3f3f3f;

struct Edge
{
    int from, to, cap, flow;
    Edge(int u, int v, int c, int f) :from(u), to(v), cap(c), flow(f) {}
    Edge() {}
};
struct Dinic
{
    int n, m, s, t;
    vector<Edge>edges;
    vector<int>G[maxv + 50];
    bool vis[maxv + 50];
    int d[maxv + 50];
    int cur[maxv + 50];

    void AddEdge(int from, int to, int cap)
    {
        edges.push_back(Edge(from, to, cap, 0));
        edges.push_back(Edge(to, from, 0, 0));
        m = edges.size();
        G[from].push_back(m - 2);
        G[to].push_back(m - 1);
    }

    void init(int n)
    {
        for (int i = 0;i <= n;i++)G[i].clear();
        edges.clear();
    }

    bool BFS()
    {
        memset(vis, 0, sizeof(vis));
        queue<int>Q;
        Q.push(s);
        d[s] = 0;
        vis[s] = 1;
        while (!Q.empty())
        {
            int x = Q.front();Q.pop();
            for (int i = 0;i < G[x].size();i++)
            {
                Edge&e = edges[G[x][i]];
                if (!vis[e.to] && e.cap > e.flow)
                {
                    vis[e.to] = 1;
                    d[e.to] = d[x] + 1;
                    Q.push(e.to);
                }
            }
        }
        return vis[t];
    }

    int DFS(int x, int a)
    {
        if (x == t || a == 0)return a;
        int flow = 0, f;
        for (int &i = cur[x];i < G[x].size();i++)
        {
            Edge&e = edges[G[x][i]];
            if (d[x] + 1 == d[e.to] && (f = DFS(e.to, min(a, e.cap - e.flow)))>0)
            {
                e.flow += f;
                edges[G[x][i] ^ 1].flow -= f;
                flow += f;
                a -= f;
                if (a == 0)break;
            }
        }
        return flow;
    }
    int Maxflow(int s, int t)
    {
        //cout << edges.size() << endl;
        this->s = s;this->t = t;
        int flow = 0;
        while (BFS())
        {
            memset(cur, 0, sizeof(cur));
            flow += DFS(s, inf);
        }
        return flow;
    }

}ans;

int d[maxn][maxn];
int K, C, M;
void floyd()
{
    for (int k = 1;k <= K + C;k++)
        for (int i = 1;i <= K + C;i++)
            for (int j = 1;j <= K + C;j++)
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
vector<int>V;
int s , t;
bool check(int num)
{
    ans.init(K + C + 5);
    for (int i = 1;i <= K;i++)
    {
        ans.AddEdge(s, i, M);
        for (int j = K + 1;j <= K + C;j++)
        {
            if(d[i][j]<=num)
                ans.AddEdge(i, j, 1);
        }
    }
    for (int j = K + 1;j <= K + C;j++)
        ans.AddEdge(j, t, 1);
    int tmp = ans.Maxflow(s, t);
    if (tmp < C)return 0;
    return 1;
}

int main()
{

    scanf("%d %d %d", &K, &C, &M);
    t = K + C + 1;
    for (int i = 1;i <= K + C;i++)
        for (int j = 1;j <= K + C;j++)
        {
            scanf("%d", &d[i][j]);
            if (d[i][j] == 0)d[i][j] = inf;
        }
    floyd();
    int r = 1e4;
    int l = 0;
    int mid;
    int ans = r;
    while (l<=r)
    {
        mid = l + r >> 1;
        if (check(mid))
            r = mid - 1, ans = mid;
        else
            l = mid + 1;
    }
    printf("%d\n", ans);
}

二分图最大权匹配有两个做法,用费用流做,或者用KM算法。KM算法会在另一篇博客详细介绍,这里贴一道模板题。
Q - Tour
这题如何建图呢,我们可以发现这每个点都会有一个入度一个出度,这和二分图不谋而合,所以只要把点拆成2个,然后用KM算法即可。
而这里需要求的是最小值,所以需要把每个权值变成负数。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxn = 205;
const int inf = 0x3f3f3f3f;
int map[maxn][maxn];

int dx[maxn], dy[maxn];
int cx[maxn], cy[maxn];
int lx[maxn], ly[maxn];
int n, m;

bool find(int u)
{
    dx[u] = 1;
    for (int i = 1;i <= n;i++)if (map[u][i] != -inf && !dy[i] && lx[u] + ly[i] == map[u][i])
    {
        dy[i] = 1;
        if (cy[i] == -1 || find(cy[i]))
        {
            cx[u] = i;
            cy[i] = u;
            return 1;
        }
    }
    return 0;
}

int KM()
{
    memset(cx, -1, sizeof(cx));
    memset(cy, -1, sizeof(cy));
    for (int i = 1;i <= n;i++)
    {
        lx[i] = -inf, ly[i] = 0;
        for (int j = 1;j <= n;j++)
        {
            if (map[i][j] == -inf)continue;
            lx[i] = max(lx[i], map[i][j]);
        }
    }

    for (int i = 1;i <= n;i++)
    {
        while (1)
        {
            memset(dx, 0, sizeof(dx));
            memset(dy, 0, sizeof(dy));
            if (find(i))break;
            int inc = inf;
            for (int j = 1;j <= n;j++)if (dx[j])
                for (int k = 1;k <= n;k++)if (!dy[k]&&map[j][k]!=-inf)
                    inc = min(inc, lx[j] + ly[k] - map[j][k]);

            for (int j = 1;j <= n;j++)
            {
                if (dx[j])lx[j] -= inc;
                if (dy[j])ly[j] += inc;
            }

        }
    }
    int ans = 0;
    for (int i = 1;i <= n;i++)if (cy[i] != -1)
        ans += map[cy[i]][i];
    return ans;
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T--)
    {
        memset(map, -inf, sizeof(map));

        scanf("%d %d", &n, &m);
        int u, v, d;
        for (int i = 1;i <= m;i++)
        {
            scanf("%d %d %d", &u, &v, &d);
            if (u == v)continue;
            map[u][v] = max(map[u][v],-d);
        }
        printf("%d\n", -KM());
    }

}

接下来就是一般图匹配带花树算法。博主蒟蒻,不会手打带花树只能看模板。。带花树算法会在另一篇博客详细介绍。
先来一道模板题。
R - Work Scheduling

#include <cstdio>  
#include <cstring>  
#include <iostream>  
#include <queue>  
using namespace std;
const int N = 250;
// 并查集维护  
int belong[N];
int findb(int x) {
    return belong[x] == x ? x : belong[x] = findb(belong[x]);
}
void unit(int a, int b) {
    a = findb(a);
    b = findb(b);
    if (a != b) belong[a] = b;
}

int n, match[N];
vector<int> e[N];
int Q[N], rear;
int _next[N], mark[N], vis[N];
// 朴素算法求某阶段中搜索树上两点x, y的最近公共祖先r  
int LCA(int x, int y) {
    static int t = 0; t++;
    while (true) {
        if (x != -1) {
            x = findb(x); // 点要对应到对应的花上去  
            if (vis[x] == t)
                return x;
            vis[x] = t;
            if (match[x] != -1)
                x = _next[match[x]];
            else x = -1;
        }
        swap(x, y);
    }
}

void group(int a, int p) {
    while (a != p) {
        int b = match[a], c = _next[b];

        // _next数组是用来标记花朵中的路径的,综合match数组来用,实际上形成了  
        // 双向链表,如(x, y)是匹配的,_next[x]和_next[y]就可以指两个方向了。  
        if (findb(c) != p) _next[c] = b;

        // 奇环中的点都有机会向环外找到匹配,所以都要标记成S型点加到队列中去,  
        // 因环内的匹配数已饱和,因此这些点最多只允许匹配成功一个点,在aug中  
        // 每次匹配到一个点就break终止了当前阶段的搜索,并且下阶段的标记是重  
        // 新来过的,这样做就是为了保证这一点。  
        if (mark[b] == 2) mark[Q[rear++] = b] = 1;
        if (mark[c] == 2) mark[Q[rear++] = c] = 1;

        unit(a, b); unit(b, c);
        a = c;
    }
}

// 增广  
void aug(int s) {
    for (int i = 0; i < n; i++) // 每个阶段都要重新标记  
        _next[i] = -1, belong[i] = i, mark[i] = 0, vis[i] = -1;
    mark[s] = 1;
    Q[0] = s; rear = 1;
    for (int front = 0; match[s] == -1 && front < rear; front++) {
        int x = Q[front]; // 队列Q中的点都是S型的  
        for (int i = 0; i < (int)e[x].size(); i++) {
            int y = e[x][i];
            if (match[x] == y) continue; // x与y已匹配,忽略  
            if (findb(x) == findb(y)) continue; // x与y同在一朵花,忽略  
            if (mark[y] == 2) continue; // y是T型点,忽略  
            if (mark[y] == 1) { // y是S型点,奇环缩点  
                int r = LCA(x, y); // r为从i和j到s的路径上的第一个公共节点  
                if (findb(x) != r) _next[x] = y; // r和x不在同一个花朵,_next标记花朵内路径  
                if (findb(y) != r) _next[y] = x; // r和y不在同一个花朵,_next标记花朵内路径  

                                                // 将整个r -- x - y --- r的奇环缩成点,r作为这个环的标记节点,相当于论文中的超级节点  
                group(x, r); // 缩路径r --- x为点  
                group(y, r); // 缩路径r --- y为点  
            }
            else if (match[y] == -1) { // y自由,可以增广,R12规则处理  
                _next[y] = x;
                for (int u = y; u != -1; ) { // 交叉链取反  
                    int v = _next[u];
                    int mv = match[v];
                    match[v] = u, match[u] = v;
                    u = mv;
                }
                break; // 搜索成功,退出循环将进入下一阶段  
            }
            else { // 当前搜索的交叉链+y+match[y]形成新的交叉链,将match[y]加入队列作为待搜节点  
                _next[y] = x;
                mark[Q[rear++] = match[y]] = 1; // match[y]也是S型的  
                mark[y] = 2; // y标记成T型  
            }
        }
    }
}

bool g[N][N];
int main() {
    scanf("%d", &n);
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++) g[i][j] = false;

    // 建图,双向边  
    int x, y; while (scanf("%d%d", &x, &y) != EOF) {
        x--, y--;
        if (x != y && !g[x][y])
            e[x].push_back(y), e[y].push_back(x);
        g[x][y] = g[y][x] = true;
    }

    // 增广匹配  
    for (int i = 0; i < n; i++) match[i] = -1;
    for (int i = 0; i < n; i++) if (match[i] == -1) aug(i);

    // 输出答案  
    int tot = 0;
    for (int i = 0; i < n; i++) if (match[i] != -1) tot++;
    printf("%d\n", tot);
    for (int i = 0; i < n; i++) if (match[i] > i)
        printf("%d %d\n", i + 1, match[i] + 1);
    return 0;
}

S - Boke and Tsukkomi
一开始题目没看懂以为就是找一些多余的边,结果wa了。。
题目的要求是同时删掉一些边之后,最大匹配数不变,而不是一次只删一条边。所以不能求最大匹配后把边删掉再求最大匹配,看两者是否一样,如果一样就说明删掉的那条是多余的。
正确做法是我们要找出所有能组成最大匹配的边,而其他的边就是多余的。那么如何找呢,设最初的最大匹配数为num,我们可以先枚举边,假定这一条边就在最大匹配中,也就是这两个点已经匹配好了,那么在图中就得删掉所有与这两个点相连的边,再跑最大匹配,看结果是否是num-1。如果是的话就说明它在最大匹配中,如果不是那就说明它就是多余的。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<stdio.h>
using namespace std;

const int N = 50;
// 并查集维护  
int belong[N];
int findb(int x) {
    return belong[x] == x ? x : belong[x] = findb(belong[x]);
}
void unit(int a, int b) {
    a = findb(a);
    b = findb(b);
    if (a != b) belong[a] = b;
}

int n,m, match[N];
int Q[N], rear;
int _next[N], mark[N], vis[N];

bool map[N][N],tmpmap[N][N];

// 朴素算法求某阶段中搜索树上两点x, y的最近公共祖先r  
int LCA(int x, int y) {
    static int t = 0; t++;
    while (true) {
        if (x != -1) {
            x = findb(x); // 点要对应到对应的花上去  
            if (vis[x] == t)
                return x;
            vis[x] = t;
            if (match[x] != -1)
                x = _next[match[x]];
            else x = -1;
        }
        swap(x, y);
    }
}

void group(int a, int p) {
    while (a != p) {
        int b = match[a], c = _next[b];

        // _next数组是用来标记花朵中的路径的,综合match数组来用,实际上形成了  
        // 双向链表,如(x, y)是匹配的,_next[x]和_next[y]就可以指两个方向了。  
        if (findb(c) != p) _next[c] = b;

        // 奇环中的点都有机会向环外找到匹配,所以都要标记成S型点加到队列中去,  
        // 因环内的匹配数已饱和,因此这些点最多只允许匹配成功一个点,在aug中  
        // 每次匹配到一个点就break终止了当前阶段的搜索,并且下阶段的标记是重  
        // 新来过的,这样做就是为了保证这一点。  
        if (mark[b] == 2) mark[Q[rear++] = b] = 1;
        if (mark[c] == 2) mark[Q[rear++] = c] = 1;

        unit(a, b); unit(b, c);
        a = c;
    }
}

// 增广  
void aug(int s) {
    for (int i = 0; i < n; i++) // 每个阶段都要重新标记  
        _next[i] = -1, belong[i] = i, mark[i] = 0, vis[i] = -1;
    mark[s] = 1;
    Q[0] = s; rear = 1;
    for (int front = 0; match[s] == -1 && front < rear; front++) {
        int x = Q[front]; // 队列Q中的点都是S型的  
        for (int i = 0; i < n; i++)if(map[x][i])
        {
            int y = i;
            if (match[x] == y) continue; // x与y已匹配,忽略  
            if (findb(x) == findb(y)) continue; // x与y同在一朵花,忽略  
            if (mark[y] == 2) continue; // y是T型点,忽略  
            if (mark[y] == 1) { // y是S型点,奇环缩点  
                int r = LCA(x, y); // r为从i和j到s的路径上的第一个公共节点  
                if (findb(x) != r) _next[x] = y; // r和x不在同一个花朵,_next标记花朵内路径  
                if (findb(y) != r) _next[y] = x; // r和y不在同一个花朵,_next标记花朵内路径  

                                                // 将整个r -- x - y --- r的奇环缩成点,r作为这个环的标记节点,相当于论文中的超级节点  
                group(x, r); // 缩路径r --- x为点  
                group(y, r); // 缩路径r --- y为点  
            }
            else if (match[y] == -1) { // y自由,可以增广,R12规则处理  
                _next[y] = x;
                for (int u = y; u != -1; ) { // 交叉链取反  
                    int v = _next[u];
                    int mv = match[v];
                    match[v] = u, match[u] = v;
                    u = mv;
                }
                break; // 搜索成功,退出循环将进入下一阶段  
            }
            else { // 当前搜索的交叉链+y+match[y]形成新的交叉链,将match[y]加入队列作为待搜节点  
                _next[y] = x;
                mark[Q[rear++] = match[y]] = 1; // match[y]也是S型的  
                mark[y] = 2; // y标记成T型  
            }
        }
    }
}

struct Edge
{
    int u, v;
}edges[125];

void init()
{
    memset(match, -1, sizeof(match));
}
vector<int>V;
void solve(int num)
{
    V.clear();
    int ans = 0;
    for (int i = 1;i <= m;i++)
    {
        init();
        int u = edges[i].u, v = edges[i].v;
        for (int j = 0;j < n;j++)
            map[u][j] = map[j][u] = map[v][j] = map[j][v] = 0;
        for (int j = 0;j < n;j++)if (match[j] == -1)aug(j);
        int tmp = 0;
        for (int j = 0;j < n;j++)if (match[j] != -1)tmp++;
        //cout << tmp << endl;
        if ((tmp / 2) < num - 1)
        {
            ans++;
            V.push_back(i);
        }
        memcpy(map, tmpmap, sizeof(tmpmap));
    }

    printf("%d\n", ans);
    int Size = V.size();
    for (int i = 0;i < Size;i++)
    {
        printf("%d", V[i]);
        if (i < Size - 1)printf(" ");
    }
    printf("\n");
}

int main()
{

    while (~scanf("%d %d",&n,&m))
    {
        memset(match, -1, sizeof(match));
        memset(map, 0, sizeof(map));
        memset(tmpmap, 0, sizeof(tmpmap));
        int u, v;
        for (int i = 1;i <= m;i++)
        {
            scanf("%d %d", &u, &v);
            u--, v--;
            edges[i].u = u, edges[i].v = v;
            map[u][v] = map[v][u] = 1;
            tmpmap[u][v] = tmpmap[v][u] = 1;
        }
        for (int i = 0;i < n;i++)if (match[i] == -1)aug(i);
        int ans = 0;
        for (int i = 0;i < n;i++)if (match[i] != -1)ans++;
        //cout << ans << endl;
        solve(ans/2);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值