文章目录
匈牙利
匈牙利模板
#include<bits/stdc++.h>
using namespace std;
const int N = 510, M = 1e5+10;
int mat[N];
bool vis[N], e[N][N];
int n, m, e_cnt;
bool dfs(int u) //dfs左部点 mat针对右部点
{
for(int v = 1; v <= m; v ++) //枚举右部点是否可再和此时的左部点匹配
{
if(e[u][v] && !vis[v]) //如果这个右部点在此次匹配中还没有遍历过才去尝试它
{
vis[v] = true;
if(!mat[v] || dfs(mat[v])) //如果它已经匹配了1~(u-1)中的点,尝试为1~(u-1)的点重新匹配
{
mat[v] = u;
return true;
}
}
}
return false;
}
int main(){
scanf("%d%d%d", &n, &m, &e_cnt);
for(int i = 1; i <= e_cnt; i ++)
{
int u, v; scanf("%d%d", &u, &v);
e[u][v] = true;
}
int ans = 0;
for(int i = 1; i <= n; i ++)
{
for(int j = 1; j <= m; j ++) vis[j] = false;
if(dfs(i)) ans ++;
}
printf("%d",ans);
return 0;
}
372. 棋盘覆盖
题目链接
找到0元素:分成两个集合,每个集合内部无连边
找到1元素:每个节点只能和一条匹配边相连
行列和为偶数作为左部点,向其相邻的4个合法点(行列和必然为奇数,作为 右部点)连边,可以发现左部点和右部点内部无连边,且每个左部点只能向右部点连一条边。
note: 棋盘问题注意行,列,行列和的奇偶性,以奇偶性来确定左部点和右部点。
#include<bits/stdc++.h>
using namespace std;
const int N = 110, T = 110;
bool g[N][N];
int n, t;
inline int id(int x, int y){return n * (x - 1) + y;}
int mat[N * N];
bool vis[N * N];
vector<int> e[N * N];
bool dfs(int u)
{
for(auto v : e[u])
{
if(vis[v]) continue;
vis[v] = true;
if(!mat[v] || dfs(mat[v]))
{
mat[v] = u;
return true;
}
}
return false;
}
int main()
{
scanf("%d%d", &n, &t);
while(t --)
{
int x, y; scanf("%d%d", &x, &y);
g[x][y] = 1;
}
for(int i = 1; i <= n; i ++)
{
for(int j = 1; j <= n; j ++)
{
if((i + j) % 2 || g[i][j]) continue; //行列和为偶数作为左部点
int l = id(i, j);
if(!(i - 1 < 1 || g[i - 1][j])) e[l].push_back(id(i - 1, j));
if(!(i + 1 > n || g[i + 1][j])) e[l].push_back(id(i + 1, j));
if(!(j - 1 < 1 || g[i][j - 1])) e[l].push_back(id(i, j - 1));
if(!(j + 1 > n || g[i][j + 1])) e[l].push_back(id(i, j + 1));
}
}
int ans = 0;
for(int i = 1; i <= n * n; i ++)
{
if(e[i].size())
{
memset(vis, 0, sizeof vis);
if(dfs(i)) ans ++;
}
}
printf("%d", ans);
return 0;
}
373. 車的放置
题目链接
将行作为左部点,列作为右部点,若该位置不禁止则从左部点向右部点连边。
374. 导弹防御塔【多重匹配拆点】
题目链接
题意: 求将敌人消灭需要的最短时间。
在二分图中需要:
找到0元素:分成两个集合,每个集合内部无连边
找到1元素:每个节点只能和一条匹配边相连
此题中一个塔可以炸多个敌人,此时不符合0元素,因此将每次轰炸(不同射出时间),即每次导弹作为一个左部点,相当于将塔进行拆点。然后二分消灭时间 m i d mid mid,若可在 m i d mid mid时间内从 u u u导弹炸到 v v v敌人,则从 u u u向 v v v连边。
note: 将塔拆点时,每个塔只需要最多拆 m m m个点,因为最多只需要炸 m m m个敌人。进行二分需要和多组测试样例一样进行初始化。
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 2510, M = 100;
int mat[M];
bool vis[M];
double t1;
int n, m, vol, t2;
struct Node
{
double x, y, out;
}a[M], b[N];
vector<int> e[N];
double dis(int i, int j)
{
return sqrt((a[i].x - b[j].x) * (a[i].x - b[j].x) + (a[i].y - b[j].y) * (a[i].y - b[j].y));
}
bool dfs(int u)
{
for(auto v : e[u])
{
if(vis[v]) continue;
vis[v] = true;
if(!mat[v] || dfs(mat[v]))
{
mat[v] = u;
return true;
}
}
return false;
}
bool check(double mid)
{
int ans = 0;
memset(mat, 0, sizeof mat);
for(int i = 1; i <= n; i ++)
{
for(int j = 0; j < m; j ++)
{
int id = i + j * n;
e[id].clear();
for(int k = 1; k <= m; k ++)
if(dis(k, id) <= vol * (mid - b[id].out)) e[id].push_back(k);
}
}
for(int i = 1; i <= n; i ++)
{
for(int j = 0; j < m; j ++)
{
int id = i + j * n;
if(!e[id].size()) continue;
memset(vis, 0, sizeof vis);
if(dfs(id)) ans ++;
}
}
return ans == m;
}
int main()
{
scanf("%d%d%lf%d%d", &n, &m, &t1, &t2, &vol);
t1 /= 60;
for(int i = 1; i <= m; i ++)
{
scanf("%lf%lf", &a[i].x, &a[i].y);
}
for(int i = 1; i <= n; i ++)
{
scanf("%lf%lf", &b[i].x, &b[i].y);
b[i].out = t1;
for(int j = 1; j < m; j ++)
{
b[i + j * n] = b[i];
b[i + j * n].out = t1 + j * (t1 + t2);
}
}
double l = 0, r = 1e6, mid;
while(r - l > 1e-9)
{
mid = (l + r) / 2;
if(check(mid)) r = mid;
else l = mid;
}
printf("%.6f", l);
return 0;
}
P1525 [NOIP2010 提高组] 关押罪犯【二分+染色法】
题目链接
存在二分图=不存在奇环=染色时连边两端颜色不同
#include<bits/stdc++.h>
using namespace std;
const int N = 20000 + 10, M = 200000 + 10;
struct E{
int to, nxt, w;
}e[M];
int head[N], cnt;
void add(int u, int v, int w)
{
e[++ cnt] = {v, head[u], w};
head[u] = cnt;
}
int color[N];
int n, m;
bool check(int u, int col, int mid)
{
color[u] = col;
for(int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].to;
if(e[i].w <= mid || color[v] == 3 - col) continue;
if(color[v] == col || !check(v, 3 - col, mid))
return false;
}
return true;
}
int main()
{
scanf("%d%d", &n, &m);
int r = 0;
for(int i = 1; i <= m; i ++)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
add(u, v, w), add(v, u, w);
r = max(r, w);
}
int l = 0, mid;
while(l < r)
{
mid = (l + r) / 2;
int fl = true;
for(int i = 1; i <= n; i ++) color[i] = 0;
for(int i = 1; i <= n; i ++)
{
if(color[i]) continue;
if(!check(i, 1, mid))
{
fl = false;
break;
}
}
if(fl) r = mid;
else l = mid + 1;
}
printf("%d", l);
return 0;
}
最大权完美匹配
Ants UVA - 1411
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 105;
double w[N][N];
double la[N], lb[N], upd[N];
bool va[N], vb[N];
int mat[N], last[N];
int n;
bool dfs(int x, int fa)
{
va[x] = true;
for(int y = 1; y <= n; y ++)
if(!vb[y])
{
if(la[x] + lb[y] - w[x][y] == 0)
{
vb[y] = true, last[y] = fa;
if(!mat[y] || dfs(mat[y], y))
{
mat[y] = x;
return true;
}
}
else if(upd[y] > la[x] + lb[y] - w[x][y])
{
upd[y] = la[x] + lb[y] - w[x][y];
last[y] = fa;
}
}
return false;
}
double KM()
{
for(int i = 1; i <= n; i ++)
{
la[i] = -1e18; lb[i] = 0, mat[i] = 0;
for(int j = 1; j <= n; j ++)
la[i] = max(la[i], w[i][j]);
}
for(int i = 1; i <= n; i ++)
{
for(int j = 1; j <= n; j ++)
upd[j] = 1e18, va[j] = vb[j] = last[j] = 0;
int st = 0; mat[0] = i;
while(mat[st])
{
double delta = 1e18;
if(dfs(mat[st], st)) break;
for(int j = 1; j <= n; j ++)
if(!vb[j] && upd[j] < delta)
{
delta = upd[j];
st = j;
}
for(int j = 1; j <= n; j ++)
{
if(va[j]) la[j] -= delta;
if(vb[j]) lb[j] += delta;
else upd[j] -= delta;
}
vb[st] = true;
}
while(st)
{
mat[st] = mat[last[st]];
st = last[st];
}
}
double ans = 0;
for(int i = 1; i <= n; i ++) ans += w[mat[i]][i];
return ans;
}
double x[N * 2], y[N * 2];
int main()
{
int fl = false;
while(~scanf("%d", &n))
{
if(!fl) fl = true;
else puts("");
for(int i = 1 + n; i <= 2 * n; i ++)
scanf("%lf%lf", &x[i], &y[i]);
for(int i = 1; i <= n; i ++)
scanf("%lf%lf", &x[i], &y[i]);
for(int i = 1; i <= n; i ++)
{
for(int j = 1; j <= n; j ++)
w[i][j] = -sqrt(pow(x[i] - x[j + n], 2) + pow(y[i] - y[j + n], 2));
}
KM();
for(int i = 1; i <= n; i ++) printf("%d\n", mat[i]);
}
}
最小点覆盖
最少的点使得每条边都被覆盖,每条匹配边选择一个点,同时选择的那些点还覆盖了所有的非匹配边。点数等于最大匹配。
因此可找出一个“2”要素:要不选择一种,要么选择另一种。
点集选择:
1.在找完最大匹配之后, 从左部每个失配点出发再执行dfs,并且对访问过的点进行标记
2. 选择左部未标记点和右部标记点则为最小点集
376. 机器任务
题目链接
note: 注意初始条件,模式为0即不需要切换模式。
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 105;
bool vis[N];
int n, m, k, mat1[N];
bool g[N][N];
bool dfs(int u)
{
for(int i = 1; i < m; i ++)
{
if(!g[u][i] || vis[i]) continue;
vis[i] = true;
if(mat1[i] == -1 || dfs(mat1[i]))
{
mat1[i] = u;
return true;
}
}
return false;
}
int main()
{
while(cin >> n >> m >> k)
{
memset(g, 0, sizeof g);
while(k --)
{
int tmp, a, b; cin >> tmp >> a >> b;
g[a][b] = true;
}
int ans = 0;
memset(mat1, -1, sizeof mat1);
for(int i = 1; i < n; i ++)
{
memset(vis, 0, sizeof vis);
if(dfs(i)) ans ++;
}
cout << ans << endl;
}
return 0;
}
377. 泥泞的区域
题目链接
此题的“2”要素:一个泥泞的位置,要么被横放的木板覆盖,要么被竖放的木板覆盖。因此每个位置可以看成一条边,连接着两个点:横放木板和竖放木板的编号,然后对木板编号进行二分匹配找出最小点覆盖。最小点数表示的是可以覆盖每个泥泞块(每条边)的木板的数量。
note: 可在边缘加上干净地板方便编号统计。
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 110, M = 11000;
int n, m;
char g[N][N];
int row_id[N][N], col_id[N][N], row_cnt, col_cnt;
vector<int> e[M];
int mat[M];
bool vis[M];
bool dfs(int u)
{
for(auto it : e[u])
{
if(vis[it]) continue;
vis[it] = true;
if(!mat[it] || dfs(mat[it]))
{
mat[it] = u;
return true;
}
}
return false;
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i ++) scanf("%s", g[i] + 1), g[i][m + 1] = '.';
for(int i = 1; i <= m; i ++) g[n + 1][i] = '.';
for(int i = 1; i <= n; i ++)
{
int start = -1;
for(int j = 1; j <= m + 1; j ++)
{
if(g[i][j] == '.')//干净
{
if(start != -1)
{
++ row_cnt;
for(int k = start; k < j; k ++) row_id[i][k] = row_cnt;
start = -1;
}
}
else if(start == -1) start = j;
}
}
for(int j = 1; j <= m; j ++)
{
int start = -1;
for(int i = 1; i <= n + 1; i ++)
{
if(g[i][j] == '.')
{
if(start != -1)
{
++ col_cnt;
for(int k = start; k < i; k ++) col_id[k][j] = col_cnt;
start = -1;
}
}
else if(start == -1) start = i;
}
}
int ans = 0;
for(int i = 1; i <= n; i ++)
{
for(int j = 1; j <= m; j ++)
{
if(g[i][j] == '.') continue;
e[row_id[i][j]].push_back(col_id[i][j]);
}
}
for(int i = 1; i <= row_cnt; i ++)
{
memset(vis, 0, sizeof vis);
if(dfs(i)) ans ++;
}
printf("%d", ans);
return 0;
}
最大独立集
选择最多的点,使得所有的点都没有公共边。将最小点集删除之后,剩下的点都无法连接。点数为原点数-最小点覆盖。
378. 骑士放置
题目链接
日型连边,每条边都是一端行列和为偶数,另一端行列和为奇数。为了让骑士互相不攻击,每条边的点只能选择一个点,也就是所有选择出来的点都没有公共边,用最大独立集。
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 110, M = 1e5;
int n, m, t;
int mat[M];
bool vis[M], g[N][N];
inline int id(int x, int y){return (x - 1) * m + y;}
int dx[8] = {1, 1, -1, -1, 2, 2, -2, -2};
int dy[8] = {2, -2, 2, -2, 1, -1, 1, -1};
vector<int> e[M];
bool dfs(int u)
{
for(auto i : e[u])
{
if(vis[i]) continue;
vis[i] = true;
if(!mat[i] || dfs(mat[i]))
{
mat[i] = u;
return true;
}
}
return false;
}
int main()
{
scanf("%d%d%d", &n, &m, &t);
for(int i = 1; i <= t; i ++)
{
int u, v; scanf("%d%d", &u, &v);
g[u][v] = 1;
}
int ans = 0;
for(int i = 1; i <= n; i ++)
{
for(int j = 1; j <= m; j ++)
{
if(g[i][j] || (i + j) % 2) continue;
int l = id(i, j);
for(int k = 0; k < 8; k ++)
{
int x = i + dx[k], y = j + dy[k];
if(x < 1 || y < 1 || x > n || y > m || g[x][y]) continue;
e[l].push_back(id(x, y));
}
}
}
for (int i = 1; i <= n; i ++ )
for(int j = 1; j <= m; j ++ )
{
if (g[i][j] || (i + j) % 2) continue;
memset(vis, 0, sizeof vis);
if(dfs(id(i, j))) ans ++;
}
printf("%d", n * m - t - ans);
return 0;
}
最小路径覆盖
在节点数为
n
n
n的有向无环图中找到最少的不相交的路径,使得所有节点都被覆盖一次。
因为路径不相交,每个点的出度和入度最多为1,又因为节点都要被覆盖,因此每个点至少有一个度(出度或入度)。可以用二分图处理。建一个新图:将点拆成
2
n
2n
2n个,原图中每条边
(
u
,
v
)
(u,v)
(u,v)改成新图中
(
u
,
v
+
n
)
(u, v+n)
(u,v+n)的边。这样新图会变成一个二分图,因为满足“01”要素:
0要素:只有左边的点会向右边的点连边,两边内部的点不会相连:连的边显然满足
1
<
=
u
<
=
n
,
1
+
n
<
=
v
+
1
<
=
2
∗
n
1<=u<=n,\space1+n<=v+1<=2*n
1<=u<=n, 1+n<=v+1<=2∗n。
1要素:左右每个点最多连一条边:二分图中匹配边
(
u
,
v
)
(u,v)
(u,v)表示
u
u
u有出度,
v
v
v有入度,而因为路径不能相交,每个点的出度和入度均不可超过1,因此每个点只能被匹配一次。
最后左部点会剩下一些没有匹配的点,它们的出度为0,是每条路径的终点,为了使得路径更少,那么非匹配点会更少,因此求出最大匹配。最小路径覆盖=
n
n
n-最大匹配。
379. 捉迷藏
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 210;
int n, m;
bool e[N][N], vis[N];
int mat[N];
bool dfs(int u)
{
for(int i = 1; i <= n; i ++)
{
if(!e[u][i] || vis[i]) continue;
vis[i] = true;
if(!mat[i] || dfs(mat[i]))
{
mat[i] = u;
return true;
}
}
return false;
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i ++){
int u, v; scanf("%d%d", &u, &v);
e[u][v] = true;
}
for(int k = 1; k <= n; k ++)
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n; j ++)
e[i][j] |= e[i][k] && e[k][j];
int ans = n;
for(int i = 1; i <= n; i ++)
{
memset(vis, 0, sizeof vis);
if(dfs(i))
{
ans --;
}
}
printf("%d", ans);
return 0;
}