总结:该专题主要分为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(
V−−√E
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);
}
}
本专题的二分图的多重匹配没啥好说的,就是建图跑最大流就行了。贴一个模板题吧。
#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);
}
}