[SOJ611] 排序【二分图匹配】

题意简述:有一个\(n\times m\)的矩阵\(a\),\([1, n\cdot m]\)的整数恰好在其中出现一次。我们需要执行以下三次操作:

  • 将\(a\)的每一行\(m\)个数任意变换顺序,得到矩阵\(b\)。
  • 将\(b\)的每一列\(n\)个数任意变换顺序,得到矩阵\(c\)。
  • 将\(c\)的每一行\(m\)个数任意变换顺序,得到矩阵\(d\)。

其中,矩阵\(d\)满足\(d_{i, j}=(i-1)\cdot m+j\)。要求输出矩阵\(b\)和\(c\)。\(1\leq n, m\leq 100\)。



考虑由矩阵\(d\)倒推回\(c, b\)。由于\(c\rightarrow d\)的过程只能任意变换每一行,因此\(c\)满足第\(i\)行构成一个\([(i-1)\cdot m+1, i\cdot m]\)的排列。

同样由于\(b\rightarrow c\)的过程只能任意变换每一列,因此\(b\)满足对于每一列\(j\),\(\lfloor\frac{b_{i, j}-1}{m}\rfloor\)构成一个\([0, n-1]\)的排列。

考虑\(a\rightarrow b\)的过程,发现我们只关心\(\lfloor\frac{a_{i, j}-1}{m}\rfloor\)的值,可以用它代替\(a_{i, j}\)。从左到右依次考虑每一列,发现我们只需要在每一行\(i\)找到一个\(x_i\),使得\(x\)构成一个\([0, n-1]\)的排列即可。

发现可以使用二分图匹配来寻找\(x\)。令左部点\(u\)为\([0, n-1]\),右部点\(v\)为每一行,\(u_i\)与\(v_j\)之间有\(w\)条边,当且仅当第\(j\)行剩余了\(w\)个元素\(i\)。

正确性证明:由于初始\([0, n-1]\)每个数都只出现\(m\)次,且每次取走的\(x\)都是一个排列,因此对于任意的\(i\in[1, m]\),第\(i\)次匹配时,每行剩余的元素个数和每个元素出现的次数均为\(m-i+1\),即,每个点的度数恰好为\(m-i+1\)(重边算多次)。

对于任意的左部点集\(S\),它的度数和为\(|S|\cdot(m-i+1)\),右部点至少需要\(|S|\)个才能装下这些度数,反之同理。因此任意同侧点集\(S\),有\(|\rm{neighbour}(S)|\geq |S|\),根据Hall定理,必然存在完美匹配。

构造\(b\)之后,\(c\)的构造显然。使用dinic进行二分图匹配,复杂度\(O(n^3\sqrt{n})\)。

#include <queue>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <cassert>
#include <iostream>
#include <algorithm>
#define R register
#define ll long long
using namespace std;
const int N = 210, M = 21000, inf = 1e9;

int n, m, tab[N][N], hd[N], nxt[M], cap[M], to[M], noedg, used[N][N], col[N][N], S, T, b[N][N];
int c[N][N], lev[N], cur[N];
queue<int> que;

template <class T> inline void read(T &x) {
    x = 0;
    char ch = getchar(), w = 0;
    while (!isdigit(ch)) w = (ch == '-'), ch = getchar();
    while (isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
    x = w ? -x : x;
    return;
}

inline bool bfs() {
    memset(lev, 0, sizeof (lev));
    memcpy(cur, hd, sizeof (cur));
    lev[S] = 1, que.push(S);
    while (que.size()) {
        int now = que.front();
        que.pop();
        for (R int i = hd[now], v; i; i = nxt[i])
            if (!lev[v = to[i]] && cap[i])
                lev[v] = lev[now] + 1, que.push(v);
    }
    return lev[T];
}

int dfs(int now, int flow) {
    if (now == T || !flow) return flow;
    int ret = 0;
    for (R int &i = cur[now], v; i; i = nxt[i]) {
        if (!cap[i] || lev[v = to[i]] != lev[now] + 1) continue;
        int fl = dfs(v, min(flow, cap[i]));
        cap[i] -= fl, cap[i ^ 1] += fl, ret += fl, flow -= fl;
        if (!flow) break;
    }
    return ret;
}

inline int dinic() {
    int ret = 0;
    while (bfs()) ret += dfs(S, inf);
    return ret;
}

void addEdg(int x, int y, int w) {
    nxt[++noedg] = hd[x], hd[x] = noedg, to[noedg] = y, cap[noedg] = w;
    nxt[++noedg] = hd[y], hd[y] = noedg, to[noedg] = x, cap[noedg] = 0;
    return;
}

int main() {
    read(n), read(m);
    for (R int i = 1; i <= n; ++i)
        for (R int j = 1; j <= m; ++j)
            read(tab[i][j]), --tab[i][j], ++col[i][tab[i][j] / m];
    S = 2 * n + 1, T = S + 1;
    for (R int j = 1; j <= m; ++j) {
        noedg = 1;
        for (R int i = 1; i <= T; ++i)
            hd[i] = 0;
        for (R int i = 1; i <= n; ++i) {
            addEdg(S, i, 1), addEdg(i + n, T, 1);
            for (R int c = 1; c <= n; ++c)
                if (col[i][c - 1])
                    addEdg(i, c + n, 1);
        }
        dinic();
        for (R int i = n + 1; i <= (n << 1); ++i) {
            for (R int e = hd[i], v; e; e = nxt[e]) {
                if ((v = to[e]) <= n && cap[e]) {
                    for (R int k = 1; k <= m; ++k)
                        if (tab[v][k] / m == i - n - 1 && !used[v][k]) {
                            b[v][j] = tab[v][k], used[v][k] = 1, --col[v][i - n - 1];
                            break;
                        }
                    break;
                }
            }
        }
    }
    for (R int i = 1; i <= n; ++i) {
        for (R int j = 1; j <= m; ++j)
            printf("%d ", b[i][j] + 1);
        printf("\n");
    }
    for (R int j = 1; j <= m; ++j) {
        for (R int i = 1; i <= n; ++i) {
            for (R int k = 1; k <= n; ++k)
                if (b[k][j] / m == i - 1) {
                    c[i][j] = b[k][j];
                    break;
                }
        }
    }
    for (R int i = 1; i <= n; ++i) {
        for (R int j = 1; j <= m; ++j)
            printf("%d ", c[i][j] + 1);
        printf("\n");
    }
    return 0;
}

原文地址:https://www.cnblogs.com/suwakow/p/11675289.html

时间: 2024-10-26 05:53:31

[SOJ611] 排序【二分图匹配】的相关文章

HDU5090--Game with Pearls(二分图匹配)

题意:给N个管子,每个管子上面有一定数目的珍珠,现在Jerry开始在管子上面再放一些珍珠,放上的珍珠数必须是K的倍数,可以不放.最后将管子排序,如果可以做到第i个管子上面有i个珍珠,则Jerry胜出,反之Tom胜出. 思路:数据比较小,所以我是水过的,模拟过程 + 贪心就能过.但正解是二分图匹配,之前没接触过. 二分图匹配:解释一下第二组样例,k = 2; 从左向右,每一个能够匹配 i 的(a),就去掉 i 周围其他的边,然后再也去掉a能指向的边,计算一下共有几个能匹配的,如果是n,则Jerry

POJ2584 T-Shirt Gumbo 二分图匹配(网络流)

1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 5 const int inf=0x3f3f3f3f; 6 const int sink=30; 7 8 struct Edge 9 { 10 int to; 11 int next; 12 int capacity; 13 14 void assign(int t,int n,int c) 15 { 16 to=t; next=n; ca

棋盘游戏(二分图匹配)

题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=1281 棋盘游戏 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 3200    Accepted Submission(s): 1897 Problem Description 小 希和Gardon在玩一个游戏:对一个N*M的棋盘,在格子里放

BZOJ 1854 游戏(二分图匹配或并查集)

此题的二分图匹配做法很容易想,就是把属性当做s集,武器当做t集,如果该武器拥有该武器则连一条边. 那么答案就是求该二分图的最大前i个匹配.将匈牙利算法改一改,当前找不到增广路就break. 但是过这个题需要常数优化,不能每次都fillchar一遍used数组.可以用队列将使用的used点加入,然后需要初始化的时候弹出即可. # include <cstdio> # include <cstring> # include <cstdlib> # include <i

HDU 3081:Marriage Match II(二分图匹配+并查集)

http://acm.hdu.edu.cn/showproblem.php?pid=3081 题意:有n个男生n个女生,他们只有没有争吵或者女生a与男生A没有争吵,且女生b与女生a是朋友,因此女生b也可以和男生A过家家(具有传递性).给出m个关系,代表女生a和男生b没有争吵过.给出k个关系,代表女生a与女生b是好朋友.每一轮过家家之后,女生只能选择可以选择并且没选过的男生过家家,问游戏能进行几轮. 思路:因为n<=100,因此支持O(n^3)的算法,挺容易想到是一个二分图匹配的.(出现在我的网络

11082 - Matrix Decompressing (网络流建模|二分图匹配)

该题是一道经典的二分图匹配的题目 .现在终于有点明白什么是二分图匹配了,其实说白了就是依赖于最大流算法之上的一种解决特定问题的算法 . 所谓二分图,就是我们假定有两个集合A和B,每个集合中有若干元素(点),其中源点与A相连,汇点与B相连,并且他们的总容量决定了最终答案的上限,所以一定要维护好 . 然后由A中的点向B中的点连线,他们之间也有一定的容量制约关系(具体看题目中的边权值限制).这样就可以求出最大流量匹配了. 有时我们要求完美匹配,即所有流入的量等于流出的量  . 该题构思极其巧妙,因为我

BZOJ 1191: [HNOI2006]超级英雄Hero(二分图匹配)

云神说他二分图匹配从来都是用网络流水过去的...我要发扬他的精神.. 这道题明显是二分图匹配.网络流的话可以二分答案+最大流.虽然跑得很慢.... ---------------------------------------------------------------------------------------- #include<cstdio> #include<cstring> #include<algorithm> #include<iostrea

二分图匹配(指派问题)

指派问题: 有N台计算机和K个任务,我们可以给每台计算机分配一个任务,每台计算机能够处理的任务种类不同,请求出最多能够处理的任务的个数. 思路:二分图匹配,可以这样来定义无向二分图,G=(UuV,E); U 代表计算机的顶点集合,V代表任务的顶点集合,对于任意u属于U和v属于V,计算机u能够处理的任务v<=>(u,v)属于E 二分图例子: 对原图做如下改变: 将原图中所有无向边e改为有向边,方向从U到V,容量1,增加源点s和汇点t,从s向所有的顶点u属于U连一条容量为1的边,从所有的顶点V属于

算法模板——二分图匹配

实现功能为二分图匹配 本程序以Codevs2776为例 详见Codevs2776 1 type 2 point=^node; 3 node=record 4 g:longint; 5 next:point; 6 end; 7 var 8 i,j,k,l,m,n:longint; 9 c,f:array[0..1000] of longint; 10 a:array[0..1000] of point; 11 procedure add(x,y:longint);inline; 12 var p: