目录
P3386 【模板】二分图最大匹配
#include <bits/stdc++.h>
using namespace std;
int n, m, e, tot, u, v, ans, head[505], pre[505]; //pre[i]存的是右部图中点i所匹配的点
bool vis[505];
//链式前向星存图
struct node{
int to;
int next;
}edge[50010];
void add_edge(int u, int v)
{
tot++;
edge[tot].to=v;
edge[tot].next=head[u];
head[u]=tot;
}
//找增广路径,判断能否给左部图中的点u找到一个匹配点
bool find(int u)
{
//遍历与点u相连的所有点(右部图中的点)
for(int i=head[u]; i!=0; i=edge[i].next){
int v=edge[i].to; //v就是右部图中与点u相连的点
//如果点v已被搜过
if(vis[v]==true) continue;
vis[v]=true;
//如果点v还未被匹配,或者给点v原来匹配的点(pre[v])能找到新的匹配点
if(!pre[v] || find(pre[v])){
pre[v]=u;
return true;
}
}
return false;
}
int main()
{
cin >> n >> m >> e;
for(int i=1; i<=e; ++i){
cin >> u >> v;
add_edge(u, v); //链式前向星存图,从左步图到右部图的一条有向边
}
for(int i=1; i<=n; ++i){
//置为false,表示所有点均未被搜过
memset(vis, 0, sizeof(vis));
if(find(i)) //对左部点 i 找增广路,如果能找到,则匹配数加一
ans++;
}
cout << ans; //输出最大匹配
return 0;
}
P1640 [SCOI2010]连续攻击游戏
#include <bits/stdc++.h>
using namespace std;
int n, u, v, tot, x, y, head[10010], match[1000010];
bool vis[1000010];
//链式前向星存图
struct node{
int to;
int next;
}edge[2000010];
void add_edge(int u, int v)
{
tot++;
edge[tot].to=v;
edge[tot].next=head[u];
head[u]=tot;
}
//找增广路,为左部图中的点u在右部图中找匹配点
bool find(int u)
{
//遍历与点u相连的点
for(int i=head[u]; i!=0; i=edge[i].next){
v=edge[i].to; //点v为与点u相连的点(在右部图中)
//如果点v在这一轮已被搜过
if(vis[v]==true) continue;
vis[v]=true; //接下来要搜来,标记为当前时间戳,表示搜过了
if(!match[v] || find(match[v])){ //如果点v没有被匹配,或者能够给点v的匹配点重新找一个匹配点
match[v]=u; //将点u与点v匹配(u在左部图中,v在右部图中)
return true; //return true 表示为点u找到了匹配点,说明有增广路,匹配数+1
}
}
return false; //当遍历完所有与点u相连的点后,仍然无法为点u寻找一个匹配,返回false
}
int main()
{
cin >> n;
for(int i=1; i<=n; ++i){
cin >> x >> y; //第i种装备的两个属性
//因为属性的范围比装备的范围小,所以属性当做左部图,装备当做右部图
add_edge(x, i);
add_edge(y, i);
}
for(int i=1; i<=10000; ++i){
memset(vis, 0, sizeof(vis));
if(find(i)==false){
cout << i-1 << endl;
return 0;
}
}
cout << 10000;
return 0;
}
P3355 骑士共存问题
#include <bits/stdc++.h>
using namespace std;
int n, m, tot, x, y, ans, id, head[40010], match[40010];
bool vis[210][210]; //有无障碍物
int flag[40010]; //二分图最大匹配打标记
int mx[8]={-1, -2, -2, -1, 1, 2, 2, 1}; //从10点钟方向开始
int my[8]={-2, -1, 1, 2, 2, 1, -1, -2}; //从12点方向开始,最后一个点会TLE
struct node{
int to;
int next;
}edge[160010]; //存边
void add_edge(int u, int v)
{
tot++;
edge[tot].to=v;
edge[tot].next=head[u];
head[u]=tot;
}
//找增广路
bool find(int u, int id)
{
for(int i=head[u]; i!=0; i=edge[i].next){
int v=edge[i].to;
if(flag[v]==id) continue; //时间戳
flag[v]=id;
if(!match[v] || find(match[v], id)){
match[v]=u;
return true;
}
}
return false;
}
int main()
{
cin >> n >> m;
for(int i=1; i<=m; ++i){
cin >> x >> y;
//(x,y)位置有障碍,不能放骑士,不能连边
vis[x][y]=true;
}
//有冲突的位置建边,待会寻找最小覆盖点(最大匹配),用节点数减去最小覆盖点就是最大独立点集
for(int i=1; i<=n; ++i){
for(int j=1; j<=n; ++j){
//(i,j)位置有障碍,这个点不能放骑士,不能连边,偶数点也不连边
if((((i+j)&1)==0) || vis[i][j]==true) continue;
//奇数点当左部图建边
for(int k=0; k<8; ++k){
int xx=i+mx[k];
int yy=j+my[k];
//如果骑士能跳到的点在棋盘内,并且该点不是障碍物,则连边
if(xx>=1 && xx<=n && yy>=1 && yy<=n && vis[xx][yy]==false){
add_edge((i-1)*n+j, (xx-1)*n+yy);
}
}
}
}
//求最小点覆盖(最大匹配)
for(int i=1; i<=n; ++i){
for(int j=1; j<=n; ++j){
id++;
if(((i+j)&1)==1 && vis[i][j]==false){ //该点是奇数点,并且该点没有障碍物
if(find((i-1)*n+j, id)){
ans++;
}
}
}
}
cout << n*n-m-ans;
return 0;
}
P5030 长脖子鹿放置
#include <bits/stdc++.h>
using namespace std;
int n, m, k, tot, x, y, ans, cnt, id, head[40010], match[40010];
bool vis[210][210]; //有无障碍物
int flag[40010]; //二分图最大匹配打标记
int mx[8]={-1, -3, -3, -1, 1, 3, 3, 1}; //从10点钟方向开始
int my[8]={-3, -1, 1, 3, 3, 1, -1, -3};
struct node{
int to;
int next;
}edge[320010]; //存边,因为奇数点到奇数点会建边,偶数点到偶数点也会建边,所以有200*200*8条边
void add_edge(int u, int v)
{
tot++;
edge[tot].to=v;
edge[tot].next=head[u];
head[u]=tot;
}
//找增广路
bool find(int u, int id)
{
for(int i=head[u]; i!=0; i=edge[i].next){
int v=edge[i].to;
if(flag[v]==id) continue; //时间戳
flag[v]=id;
if(!match[v] || find(match[v], id)){
match[v]=u;
return true;
}
}
return false;
}
int main()
{
cin >> n >> m >> k;
for(int i=1; i<=k; ++i){
cin >> x >> y;
//(x,y)位置有障碍,不能放长脖子鹿,不能连边
//可能多个障碍物在同一个位置,所以需要统计有多少个位置有障碍物
if(vis[x][y]==false){
cnt++;
vis[x][y]=true;
}
}
//有冲突的位置建边,待会寻找最小覆盖点(最大匹配),用节点数减去最小覆盖点就是最大独立点集
for(int i=1; i<=n; ++i){
for(int j=1; j<=m; ++j){
//(i,j)位置有障碍,这个点不能放长脖子鹿,不能连边
if(vis[i][j]==true) continue;
for(int k=0; k<8; ++k){
int xx=i+mx[k];
int yy=j+my[k];
//如果长脖子鹿能跳到的点在棋盘内,并且该点不是障碍物,则连边
if(xx>=1 && xx<=n && yy>=1 && yy<=m && vis[xx][yy]==false){
add_edge((i-1)*n+j, (xx-1)*n+yy);
}
}
}
}
//求最小点覆盖(最大匹配)
for(int i=1; i<=n; ++i){
for(int j=1; j<=m; ++j){
id++;
if(vis[i][j]==false){ //该点没有障碍物
if(find((i-1)*n+j, id)){
ans++;
}
}
}
}
//因为奇数点和奇数点相连、偶书点和偶数点相连,计算最小覆盖时相当于多算了一遍
//所以最小覆盖为ans/2,
cout << n*m-cnt-ans/2;
return 0;
}
P1525 [NOIP2010 提高组] 关押罪犯
#include<bits/stdc++.h>
using namespace std;
const int N=20010;
const int M=100010;
int n, m, color[N];
bool flag;
vector<int> e[N];
struct Node{
int x, y, v;
bool operator < (const Node &other) const{
return v < other.v;
}
}a[M];
void dfs(int u, int c) {
color[u]=c;
for (int i=0; i < e[u].size(); i++){
int v=e[u][i];
if (!color[v]) dfs(v,3-c);
else
if (color[v]==c) flag=false;
}
}
bool check(int pos) {
for (int i=1; i <=n; i++) e[i].clear();
for (int i=pos+1; i <=m; i++) {
e[a[i].x].push_back(a[i].y);
e[a[i].y].push_back(a[i].x);
}
flag=true;
memset(color, 0, sizeof color);
for (int i=1; i <=n; i++)
if (!color[i]){
dfs(i, 1);
if (!flag) return false;
}
return true;
}
int main() {
cin >> n >> m;
for (int i=1; i <=m; i++)
cin >> a[i].x >> a[i].y >> a[i].v;
sort(a+1, a+1+m);
int l=0, r=m, mid;
while (l+1<r) {
mid=(l+r) / 2;
if (check(mid)) r=mid;
else l=mid;
}
if(m==1) cout << "0";
else cout << a[r].v;
return 0;
}
373. 車的放置
#include <bits/stdc++.h>
using namespace std;
int n, m, t, x, y, ans, tot, head[201], match[201]; //match[i]表示右部点i匹配的左部点,为0表示还没有匹配
//vis[i]表示对右部点i是否进行过一轮匹配查找, ban[i][j]为true表示(x,y)位置不能放置
bool vis[201], ban[201][201];
//链式前向星存图
struct node{
int to,next;
}edge[40001];
void add_edge(int u, int v)
{
tot++;
edge[tot].to=v;
edge[tot].next=head[u];
head[u]=tot;
}
//寻找增广路,给左部点u找一个匹配
bool find(int u)
{
//遍历与u相连的右部点
for(int i=head[u]; i!=0; i=edge[i].next){
int v=edge[i].to;
if(!vis[v]){ //如果对右部点v没有进行过匹配查找
vis[v]=true;
//如果右部点v还没有被匹配,或者右部点匹配的左部点还能找到新的匹配
if(!match[v] || find(match[v])){
match[v]=u; //将右部点v和左部点u匹配
return true; //返回true,表示给左部点u找到了一个匹配
}
}
}
return false;
}
int main()
{
cin >> n >> m >> t; //N行M列的棋盘,t个格子禁止放置。
for(int i=1; i<=t; ++i){
cin >> x >> y;
ban[x][y]=true; //(x,y)禁止放置車
}
//行的编号作为点,充当左部图
for(int i=1; i<=n; ++i){
//列的编号作为点,充当右部图
for(int j=1; j<=m; ++j){
//如果位置(i, j)可以放車,则第i行和第j列连一条边
//因为每一行每一列只能放一个車,相当于左部点和右部点中的点,最多都只能连一条边
if(!ban[i][j]){
add_edge(i, j);
}
}
}
for(int i=1; i<=n; ++i){
memset(vis, 0, sizeof(vis));
if(find(i))
ans++;
}
cout << ans;
return 0;
}
374. 导弹防御塔
#include <bits/stdc++.h>
using namespace std;
int n, m, tot, head[2510], match[2510];
bool vis[2510];
double t1, t2, v, ans, x[2510], y[2510], x2[2510], y2[2510], f[51][51];
double cal(int i, int j)
{
if(f[i][j]!=0) return f[i][j];
return f[i][j]=sqrt((x[i]-x2[j])*(x[i]-x2[j]) + (y[i]-y2[j])*(y[i]-y2[j]))/v;
}
struct node{
int to;
int next;
double dis;
}edge[1250100];
void add_edge(int u, int v, double w)
{
tot++;
edge[tot].to=v;
edge[tot].dis=w;
edge[tot].next=head[u];
head[u]=tot;
}
bool find(int u, double limit)
{
for(int i=head[u]; i; i=edge[i].next){
int vv=edge[i].to;
double ww=edge[i].dis;
if(vis[vv]==false && ww<=limit){
vis[vv]=true;
if(!match[vv] || find(match[vv], limit)){
match[vv]=u;
return true;
}
}
}
return false;
}
bool check(double limit)
{
//对于每次验证在limit的时限内能否拦截所有的入侵者
//需要初始化match数组
memset(match, 0, sizeof(match));
for(int i=1; i<=m; ++i){
//每次找增广路时,需要初始化vis数组
memset(vis, 0, sizeof(vis));
if(find(i, limit)==false){ //如果某个入侵者无法被拦截
return false;
}
}
return true;
}
int main()
{
// freopen("asd.txt", "r", stdin);
cin >> n >> m >> t1 >> t2 >> v;
t1=t1/60; //单位转换为分钟
//入侵者的坐标
for(int i=1; i<=m; ++i){
cin >> x[i] >> y[i];
}
//防御塔的坐标
for(int i=1; i<=n; ++i){
cin >> x2[i] >> y2[i];
}
//建边,入侵者放在左部图,防御塔放在有部图,因为防御塔要扩展为多个
for(int i=1; i<=m; ++i){ //枚举入侵者,每一个入侵者要和每一座防御塔建边
for(int j=1; j<=n; ++j){ //枚举防御塔
for(int k=0; k<m; ++k){ //每座防御塔最多发射m次就可以打掉所有入侵者
//入侵者的编号为i,防御塔的编号为j+k*n+j,其中k表示防御塔j第几次再发射
add_edge(i, j+k*n, t1+cal(i, j)+k*(t1+t2));
}
}
}
//测试check()函数是否正确
// for(double i=0; i<=100; i=i+0.001){
// if(check(i)==1){
// printf("%.6lf", i);
// break;
// }
// }
//二分答案
double l=t1, r=100000000, mid;
while(r-l>0.0000001){
mid=(l+r)/2;
if(check(mid)){
r=mid;
}
else{
l=mid;
}
}
printf("%.6lf", mid);
return 0;
}
P2055 [ZJOI2009]假期的宿舍
#include <bits/stdc++.h>
using namespace std;
int t, n, ans, cnt, tot, home[51], head[51], match[51];
bool stu[51], vis[51], know[51][51];
struct node{
int to;
int next;
}edge[1251];
void add_edge(int u, int v)
{
tot++;
edge[tot].to=v;
edge[tot].next=head[u];
head[u]=tot;
}
bool find(int u)
{
for(int i=head[u]; i; i=edge[i].next){
int v=edge[i].to;
if(!vis[v]){
vis[v]=true;
if(!match[v] || find(match[v])){
match[v]=u;
return true;
}
}
}
return false;
}
int main()
{
cin >> t;
while(t--){
cin >> n;
tot=0; //清空链式前向星存的图
ans=0; //能配对的人——床数
cnt=0; //需要住宿的学生数
memset(head, 0, sizeof(head));
memset(match, 0, sizeof(match)); //match[i]放的是i同学的床匹配给的同学
memset(stu, 0, sizeof(stu)); //是否是在校学生
memset(home, 0, sizeof(home)); //是否回家
memset(know, 0, sizeof(know)); //是否认识
//是否是在校学生,0表示不是,1表示是
for(int i=1; i<=n; ++i){
cin >> stu[i];
}
//是否回家,0表示不回家,1表示回家,其他表示不是在校学生
for(int i=1; i<=n; ++i){
cin >> home[i];
}
for(int i=1; i<=n; ++i){
for(int j=1; j<=n; ++j){
//第i个人和第j个人是否认识
cin >> know[i][j];
//处理自己和自己认识
if(i==j) know[i][j]=1;
}
}
//建边,左部图为1到n个同学,右部图为1到n个同学
//左边为准备入住的同学,右边为有床的同学
for(int i=1; i<=n; ++i){
for(int j=1; j<=n; ++j){
//i同学和j同学认识 并且 i同学不回家(在校生不回家或者外校同学),需要床 并且 j同学有床(是学校的学生)
if(know[i][j] && (stu[i]&&!home[i] || !stu[i]) && stu[j]){ //这个细节好坑啊
add_edge(i, j);
}
}
}
for(int i=1; i<=n; ++i){
memset(vis, 0, sizeof(vis));
//如果左部点里的i同学没回家(在校生不回家或者外校同学),说明需要找匹配
if(stu[i]&&!home[i] || !stu[i]){ //这个细节好坑啊
cnt++;
if(find(i)){
ans++;
}
else{ //只要有个同学找不到床,接没必要继续找下去
break;
}
}
}
if(ans==cnt){ //所有同学都有住的地方
cout << "^_^" << endl;
}
else{
cout << "T_T" << endl;
}
}
return 0;
}
P1129 [ZJOI2007] 矩阵游戏
//和車的放置很像
#include <bits/stdc++.h>
using namespace std;
int t, n, x, ans, tot, head[201], match[201];
int flag[201];
struct node{
int to;
int next;
}edge[40001];
void add_edge(int u, int v)
{
tot++;
edge[tot].to=v;
edge[tot].next=head[u];
head[u]=tot;
}
bool find(int u, int tag)
{
for(int i=head[u]; i!=0; i=edge[i].next){
int v=edge[i].to;
if(flag[v]!=tag){
flag[v]=tag;
if(!match[v] || find(match[v], tag)){
match[v]=u;
return true;
}
}
}
return false;
}
int main()
{
cin >> t;
while(t--){
cin >> n;
tot=0;
ans=0;
memset(match, 0, sizeof(match));
memset(head, 0, sizeof(head));
memset(flag, 0, sizeof(flag));
//行号作为左部点, 列号作为右部点
for(int i=1; i<=n; ++i){
for(int j=1; j<=n; ++j){
cin >> x;
if(x==1){
add_edge(i, j);
}
}
}
for(int i=1; i<=n; ++i){
// memset(vis, 0, sizeof(vis));
if(find(i, i)){
ans++;
}
}
if(ans==n){
cout << "Yes" << endl;
}
else{
cout << "No" << endl;
}
}
return 0;
}
P2319 [HNOI2006]超级英雄
#include <bits/stdc++.h>
using namespace std;
int n, m, x, y, ans, tot, head[1001], vis[1001], match[1001], match1[1001];
bool link[1001][1001];
struct node{
int to, next;
}edge[2002];
void add_edge(int u, int v)
{
tot++;
edge[tot].to=v;
edge[tot].next=head[u];
head[u]=tot;
}
bool find(int u, int tag)
{
for(int i=head[u]; i; i=edge[i].next){
int v=edge[i].to;
if(vis[v]!=tag){
vis[v]=tag;
if(!match[v] || find(match[v], tag)){
match[v]=u;
return true;
}
}
}
return false;
}
int main()
{
cin >> n >> m;
for(int i=1; i<=m; ++i){
cin >> x >> y;
if(!link[i][x]){
add_edge(i, x);
link[i][x]=true;
}
if(!link[i][y]){
add_edge(i, y);
link[i][y]=true;
}
}
//题目作为左部点, 妙计作为右部点
for(int i=1; i<=m; ++i){
if(find(i, i))
ans++;
else
break;
}
cout << ans << endl;
for(int i=0; i<n; ++i){
//第i个锦囊妙计解决第match[i]道题
if(match[i]!=-1){
//match1[x]表示第x道题用的锦囊妙计
match1[match[i]]=i;
}
}
for(int i=1; i<=ans; ++i){
cout << match1[i] << endl;
}
return 0;
}
P2016 战略游戏
//最小点覆盖问题=最大匹配
#include <bits/stdc++.h>
using namespace std;
int n, k, tot, ans, x, y, head[1501], match[1501], vis[1501];
struct node{
int to, next;
}edge[3000]; //双向建边, 需要为1500*2
void add_edge(int u, int v)
{
tot++;
edge[tot].to=v;
edge[tot].next=head[u];
head[u]=tot;
}
bool find(int u, int tag)
{
for(int i=head[u]; i; i=edge[i].next){
int v=edge[i].to;
if(vis[v]!=tag){
vis[v]=tag;
if(!match[v] || find(match[v], tag)){
match[v]=u;
return true;
}
}
}
return false;
}
int main()
{
cin >> n;
for(int i=1; i<=n; ++i){
cin >> x >> k;
while(k--){
cin >> y;
//将编号变为1
add_edge(x+1, y+1);
add_edge(y+1, x+1);
}
}
for(int i=1; i<=n; ++i){
if(find(i, i))
ans++;
}
//无向图, 匈牙利算法算出的最大匹配是真实的两倍, 所以需要除以2
cout << ans/2;
return 0;
}