一般图的最大匹配-带花树算法 Tutte矩阵

带花树算法

配套食用:

  1. ljh2000-jump
  2. OI界第一麻瓜

论文作者: vfleaking
详细讲解: yihuikang

这里有一道范围 1 e 4 1e4 1e4的非常有意思的一般图最大权匹配的题,老板们可以去艹一艹这道题:winter camp day8 div2 B题

UOJ79模板

//75ms
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
inline int getint() {
    int w = 0, q = 0;
    char c = getchar();
    while ((c < '0' || c > '9') && c != '-') c = getchar();
    if (c == '-') q = 1, c = getchar();
    while (c >= '0' && c <= '9') w = w * 10 + c - '0', c = getchar();
    return q ? -w : w;
}
class Dhs {
   public:
    static const int D_MXN = 520;
    static const int D_MXE = 300005;
    static const int D_MXQL = 10005;
    int n, m;
    int eCnt, head[D_MXN], nex[D_MXE], to[D_MXE];
    int dui[D_MXQL], hed, tail;
    int fa[D_MXN], id[D_MXN], pre[D_MXN], match[D_MXN], vis[D_MXN], Tim;
    inline void add_edge(int u, int v) {
        nex[++eCnt] = head[u];
        to[eCnt] = v;
        head[u] = eCnt;
    }
    inline int Fi(int x) {
        if (fa[x] != x) fa[x] = Fi(fa[x]);
        return fa[x];
    }
    inline int lca(int x, int y) {
        Tim++;
        while (vis[x] != Tim) {
            if (x) {
                x = Fi(x);
                if (vis[x] == Tim) return x;
                vis[x] = Tim;
                if (match[x] != 0)
                    x = Fi(pre[match[x]]);
                else
                    x = 0;
            }
            swap(x, y);
        }
        return x;
    }

    inline void change(int x, int y, int k) {  
        //把奇环上的点缩成一个点,并且把原来是奇点的点变成偶点,加入队列
        while (Fi(x) != k) {
            pre[x] = y;
            int z = match[x];
            if (id[z] == 1) {
                id[z] = 0;
                dui[++tail] = z;
            }
            if (Fi(z) == z) fa[z] = k;
            if (Fi(x) == x) fa[x] = k;
            y = z;
            x = pre[y];
        }
    }

    inline bool bfs(int ini) {
        for (int i = 1; i <= n; i++) id[i] = -1, fa[i] = i;
        hed = tail = 0;
        dui[++tail] = ini;
        id[ini] = 0;
        int u;
        while (hed < tail) {
            u = dui[++hed];
            for (int i = head[u]; i; i = nex[i]) {
                int v = to[i];
                if (id[v] == -1) {
                    pre[v] = u;
                    id[v] = 1;
                    if (!match[v]) {
                        int last, t, now = v;
                        while (now != 0) {
                            t = pre[now];
                            last = match[t];
                            match[t] = now;
                            match[now] = t;
                            now = last;
                        }
                        return true;
                    }
                    id[match[v]] = 0;
                    dui[++tail] = match[v];
                } else if (id[v] == 0 && Fi(u) != Fi(v)) {  
                    //出现奇环且不是在同一个环中
                    int g = lca(u, v);
                    change(u, v, g);
                    change(v, u, g);
                }
            }
        }
        return false;
    }
    void init() {
        eCnt = Tim = 0;
        for (int i = 0; i <= 500; ++i)
            assert(head[i] == 0 && match[i] == 0 && vis[i] == 0 && pre[i] == 0);
    }
    inline void work() {
        init();
        n = getint();
        m = getint();
        int x, y;
        for (int i = 1; i <= m; i++) {
            x = getint();
            y = getint();
            add_edge(x, y);
            add_edge(y, x);
        }
        int ans = 0;
        for (int i = 1; i <= n; i++)
            if (!match[i] && bfs(i)) ans++;
        printf("%d\n", ans);
        for (int i = 1; i <= n; i++) printf("%d ", match[i]);
        puts("");
    }

} dhs;
int main() {
    dhs.work();
    system("pause");
    return 0;
}

//74ms
//https://www.cnblogs.com/owenyu/p/6858508.html
#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
using namespace std;
int read() {
    int x=0,f=1;
    char c=getchar();
    for (;!isdigit(c);c=getchar()) if (c=='-') f=-1;
    for (;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*f;
}
const int maxn=505;
const int maxm=maxn*maxn*2;
int n,m,que[maxm],ql,qr,pre[maxn],tim=0;
struct edge {
    int v,nxt;
} e[maxm];
int h[maxn],tot=0;
int match[maxn],f[maxn],tp[maxn],tic[maxn];
int find(int x) {
    return f[x]==x?f[x]:f[x]=find(f[x]);
}
void add(int u,int v) {
    e[++tot]=(edge){v,h[u]};
    h[u]=tot;
}
int lca(int x,int y) {
    for (++tim;;swap(x,y)) if (x) {
        x=find(x);
        if (tic[x]==tim) return x; else tic[x]=tim,x=pre[match[x]];
    }
}
void shrink(int x,int y,int p) {
    while (find(x)!=p) {
        pre[x]=y,y=match[x];
        if (tp[y]==2) tp[y]=1,que[++qr]=y;
        if (find(x)==x) f[x]=p;
        if (find(y)==y) f[y]=p;
        x=pre[y];
    }
}
bool aug(int s) {
    for (int i=1;i<=n;++i) f[i]=i;
    memset(tp,0,sizeof tp),memset(pre,0,sizeof pre);
    tp[que[ql=qr=1]=s]=1; // 1: type A ; 2: type B
    int t=0;
    while (ql<=qr) {
        int x=que[ql++];
        for (int i=h[x],v=e[i].v;i;i=e[i].nxt,v=e[i].v) {
            if (find(v)==find(x) || tp[v]==2) continue; 
            if (!tp[v]) {
                tp[v]=2,pre[v]=x;
                if (!match[v]) {
                    for (int now=v,last,tmp;now;now=last) {
                        last=match[tmp=pre[now]];
                        match[now]=tmp,match[tmp]=now;
                    }
                    return true;
                } 
                tp[match[v]]=1,que[++qr]=match[v];
            } else if (tp[v]==1) {
                int l=lca(x,v);
                shrink(x,v,l);
                shrink(v,x,l);
            }
        }
    }   
    return false;
}
int main() {
    n=read(),m=read();
    for (int i=1;i<=m;++i) {
        int x=read(),y=read();
        add(x,y),add(y,x);
    }
    int ans=0;
    for (int i=1;i<=n;++i) ans+=(!match[i] && aug(i));
    printf("%d\n",ans);
    for (int i=1;i<=n;++i) printf("%d ",match[i]);
    puts("");
    return 0;
}

题目

14多校9B题,HDU4687 Boke and Tsukkomi

timus1099

传送门

zoj 3316 Game

题意: 棋盘上有一些棋子,两位选手轮流移除棋子,后者移除的棋子和前者移动的棋子 曼哈顿距离之差不能小于L。不能移除棋子者输。问后手是否能赢?
解题思路: 能连续移除棋子之间连一条边,求一般图最大匹配M。若M * 2 = N,后手胜

hdu 3446/toj 3557 daizhenyang’s chess

题意: 棋盘上有个king 可以往周围20个合法地方移动。给出棋盘,某些位置不能移到。 两位选手轮流移动king,不能移动到先前到达过的位置,不能移动者输。问先手是否能赢?
解题思路:能够合法相互移动的位置连边。将起点去掉,求一般图最大匹配。将起点加入, 寻找可增广路,若存在可增广路,则先手胜。(求两次最大图匹配也行)

hdu 3551 hard problem

题意: 给出一个无向图,存在重边,没有自环。问能否删除一些边,使得每个顶点的度数为, 指定度数deg[i]?
解题思路1: 我们应该删除哪些边呢? 预处理每个顶点的度数d[i], 若d[i] = deg[i], 那么 与这个点相连的边是不能删掉的。原因很显然。若i与j之间有边,并且d[i]>deg[i], d[j]>deg[j]那么这条边是可以删除的。接下来如何建图呢? 将每个点i 拆成 d[i] - deg[i]个点。如果i与j之间的边e可以删除, 则边e与i、j拆出的每个点连一条边 ei, ej(重边连多次)。然后求该一般图最大匹配,若存在完美匹配,则YES。
解题思路2:
把一条边拆成两个点,把每个点u拆成du[u]个点
edge ->e1 e2
x -> x1
y -> y1 y2
(e1, e2), (x1, e1), (e2, y1), (e2, y2)
再跑一般图最大匹配
看是否是完备匹配
如果存在完美匹配的话,一定可以是每条边拆成两个点分别和两个端点拆成
的du[i]个点中的一个相匹配。
如果完备匹配的话,证明说有条件都满足。
如果du[i]等于任意0或者整数, 也是可以这样写的
如果du[i]等于0的话,相当于i相邻的边拆成的两个点自己和自己相匹配

/*
链接
https://ac.nowcoder.com/acm/contest/5666/I
题意
n=50
m=100
小明要求每个点的度数为 du[i] = 1 or 2, 问你是否存在这样一个边的子集
满足上述条件。
不存在自环,可能有重边
思路
把一条边拆成两个点,把每个点u拆成du[u]个点
edge ->e1 e2
x -> x1
y -> y1 y2
(e1, e2), (x1, e1), (e2, y1), (e2, y2)
再跑一般图最大匹配
看是否是完备匹配
如果存在完美匹配的话,一定可以是每条边拆成两个点分别和两个端点拆成
的du[i]个点中的一个相匹配。
如果完备匹配的话,证明说有条件都满足。

备注
如果du[i]等于任意0或者整数, 也是可以这样写的
如果du[i]等于0的话,相当于i相邻的边拆成的两个点自己和自己相匹配
*/
#include<bits/stdc++.h>
using namespace std;
const int MXN = 2e2 + 5;
class Dhs {
   public:
    static const int D_MXN = 5020;
    static const int D_MXE = 100005;
    static const int D_MXQL = 20005;
    int n;
    int eCnt, head[D_MXN], nex[D_MXE], to[D_MXE];
    int dui[D_MXQL], hed, tail;
    int fa[D_MXN], id[D_MXN], pre[D_MXN], match[D_MXN], vis[D_MXN], Tim;
    inline void add_edge(int u, int v) {
        nex[++ eCnt] = head[u];
        to[eCnt] = v;
        head[u] = eCnt;
        nex[++ eCnt] = head[v];
        to[eCnt] = u;
        head[v] = eCnt;
    }
    inline int Fi(int x) {
        if (fa[x] != x) fa[x] = Fi(fa[x]);
        return fa[x];
    }
    inline int lca(int x, int y) {
        ++ Tim;
        while (vis[x] != Tim) {
            if (x) {
                x = Fi(x);
                if (vis[x] == Tim) return x;
                vis[x] = Tim;
                if (match[x] != 0)
                    x = Fi(pre[match[x]]);
                else
                    x = 0;
            }
            swap(x, y);
        }
        return x;
    }
    inline void change(int x, int y, int k) {  
        //把奇环上的点缩成一个点,并且把原来是奇点的点变成偶点,加入队列
        while (Fi(x) != k) {
            pre[x] = y;
            int z = match[x];
            if (id[z] == 1) {
                id[z] = 0;
                dui[++tail] = z;
            }
            if (Fi(z) == z) fa[z] = k;
            if (Fi(x) == x) fa[x] = k;
            y = z;
            x = pre[y];
        }
    }

    inline bool bfs(int ini) {
        for (int i = 1; i <= n; i++) id[i] = -1, fa[i] = i;
        hed = tail = 0;
        dui[++tail] = ini;
        id[ini] = 0;
        int u;
        while (hed < tail) {
            u = dui[++hed];
            for (int i = head[u]; i; i = nex[i]) {
                int v = to[i];
                if (id[v] == -1) {
                    pre[v] = u;
                    id[v] = 1;
                    if (!match[v]) {
                        int last, t, now = v;
                        while (now != 0) {
                            t = pre[now];
                            last = match[t];
                            match[t] = now;
                            match[now] = t;
                            now = last;
                        }
                        return true;
                    }
                    id[match[v]] = 0;
                    dui[++tail] = match[v];
                } else if (id[v] == 0 && Fi(u) != Fi(v)) {  
                    //出现奇环且不是在同一个环中
                    int g = lca(u, v);
                    change(u, v, g);
                    change(v, u, g);
                }
            }
        }
        return false;
    }
    void init(int _n) {
        n = _n;
        eCnt = Tim = 0;
        for (int i = 1; i <= n; ++i) {
            head[i] = match[i] = vis[i] = pre[i] = 0;
        }
    }
    int max_match() {
        int ans = 0;
        for (int i = 1; i <= n; i++) if (!match[i] && bfs(i)) ++ans;
        // printf("%d\n", ans);
        // for (int i = 1; i <= n; i++) printf("%d ", match[i]);
        // puts("");
        return ans;
    }
} dhs;
int n, m, sum_du, inde;
int id[MXN][MXN], contest[MXN][2], du[MXN];
int ein[MXN][2];
int main() {
    int tim = read(), cas = 0;
    while(tim --) {
        n = read(), m = read();
        sum_du = inde = 0;
        for(int i = 1; i <= n; ++i) du[i] = 0;
        for(int i = 1; i <= m; ++i) {
            ein[i][0] = read();
            ein[i][1] = read();
            ++ du[ein[i][0]];
            ++ du[ein[i][1]];
            contest[i][0] = ++ inde;
            contest[i][1] = ++ inde;
        }
        int flag = 0;
        for(int i = 1; i <= n; ++i) {
            id[i][0] = read();
            if(id[i][0] > du[i]) flag = 1;
            if(flag) continue;
            sum_du += id[i][0];
            for(int j = 1; j <= id[i][0]; ++j) {
                id[i][j] = ++ inde;
            }
        }
        printf("Case %d: ", ++ cas);
        if(flag || sum_du % 2 == 1) {
            printf("NO\n");
            continue;
        }
        dhs.init(inde);
        for(int i = 1, a, b; i <= m; ++i) {
            a = ein[i][0], b = ein[i][1];
            dhs.add_edge(contest[i][0], contest[i][1]);
            for(int j = 1; j <= id[a][0]; ++j) {
                dhs.add_edge(id[a][j], contest[i][0]);
            }
            for(int j = 1; j <= id[b][0]; ++j) {
                dhs.add_edge(id[b][j], contest[i][1]);
            }
        }
        printf("%s\n", dhs.max_match() == inde / 2?"YES":"NO");
    }
#ifndef ONLINE_JUDGE
    system("pause");
#endif
    return 0;
}

Tutte矩阵

叉姐模板
2020牛客多校第一场 i 题

#include <bits/stdc++.h>

const int N = 50;
const int M = 100;
const int MOD = 1e9 + 7;

int inverse(int a) {
  return a == 1 ? 1
                : static_cast<uint64_t>(MOD - MOD / a) * inverse(MOD % a) % MOD;
}

template<int V>
int tutte(int n, const std::vector<std::pair<int, int>> &edges, int seed) {
  static int mat[V][V];
  memset(mat, 0, sizeof(mat));
  std::mt19937 gen(seed);
  std::uniform_int_distribution<int> dist(1, MOD - 1);
  for (auto &&e : edges) {
    auto x = dist(gen);
    mat[e.first][e.second] = x;
    mat[e.second][e.first] = MOD - x;
  }
  int rank = 0;
  for (int j = 0; j < n; ++j) {
    int pivot = rank;
    while (pivot < n && !mat[pivot][j]) {
      pivot++;
    }
    if (pivot < n) {
      for (int k = 0; k < n; ++k) {
        std::swap(mat[rank][k], mat[pivot][k]);
      }
      const uint64_t inv = inverse(mat[rank][j]);
      for (int i = rank + 1; i < n; ++i) {
        if (mat[i][j]) {
          const uint64_t tmp = inv * mat[i][j] % MOD;
          for (int k = j; k < n; ++k) {
            mat[i][k] += MOD - tmp * mat[rank][k] % MOD;
            if (mat[i][k] >= MOD) {
              mat[i][k] -= MOD;
            }
          }
        }
      }
      rank++;
    }
  }
  return rank;
}

#ifndef NO_MAIN

int deg[N];

int main() {
  int n, m;
  while (scanf("%d%d", &n, &m) == 2) {
    int demand = 0;
    for (int i = 0; i < n; ++i) {
      scanf("%d", deg + i);
      demand += deg[i];
    }
    std::vector<std::pair<int, int>> edges;
    for (int i = 0, a, b; i < m; ++i) {
      scanf("%d%d", &a, &b);
      a--, b--;
      if (deg[a] == 2) {
        std::swap(a, b);
      }
      if (deg[a] == 2) {
        edges.emplace_back(a << 1 | 0, n + i << 1 | 0);
        edges.emplace_back(a << 1 | 1, n + i << 1 | 0);
        edges.emplace_back(b << 1 | 0, n + i << 1 | 1);
        edges.emplace_back(b << 1 | 1, n + i << 1 | 1);
        edges.emplace_back(n + i << 1 | 0, n + i << 1 | 1);
        demand += 2;
      } else if (deg[b] == 2) {
        edges.emplace_back(a << 1, b << 1 | 0);
        edges.emplace_back(a << 1, b << 1 | 1);
      } else {
        edges.emplace_back(a << 1, b << 1);
      }
    }
    puts(tutte<(N + M) << 1>(n + m << 1, edges, 0) == demand ? "Yes" : "No");
  }
}
#endif
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值