先上一些概念:
二分图最大匹配的板子,复杂度是(V*E)点数乘以边数:
const int maxn=409;
const int maxm=1e5+7;
struct Edge{
int v,next;
}edge[maxm];
int head[maxn],top;
void init(){
memset(head,-1,sizeof(head));
top=0;
}
void add(int u,int v){
edge[top].v=v;
edge[top].next=head[u];
head[u]=top++;
}
int match[maxn];
bool vis[maxn];
bool dfs(int u){
int v;
for(int i=head[u];i!=-1;i=edge[i].next){
v=edge[i].v;
if(!vis[v]){
vis[v]=1;
if(!match[v]||dfs(match[v])){
match[v]=u;
return 1;
}
}
}
return 0;
}
int main(){
int res=0;//答案;
for(int i=1;i<=n;++i){//一边的点;
memset(vis,0,sizeof(vis));
if(dfs(i)) ++res;
}
return 0;
}
二分图匹配的模型有两个要素:
1、节点能分成独立的两个集合,每个集合内部有0条边。
2、每个节点只能与1条匹配边匹配。
一、257. 关押罪犯
显然这个答案具有单调性,所以我们可以二分答案,然后比答案大的两个人显然不可以分到一起,所以我们建图,将两个人连边,然后跑一下无向图染色就行了,不用匈牙利。
#include
using namespace std;
const int maxm=1e5+7;
const int maxn=2e4+7;
struct Node{
int u,v,w;
}node[maxm];
struct Edge{
int v,next;
}edge[maxm<<1];
int head[maxn],top;
void init(){
memset(head,-1,sizeof(head));
top=0;
}
void add(int u,int v){
edge[top].v=v;
edge[top].next=head[u];
head[u]=top++;
}
int n,m;
bool color[maxn],vis[maxn];
bool dfs(int u,bool f){
color[u]=f;
vis[u]=1;
int v;
for(int i=head[u];i!=-1;i=edge[i].next){
v=edge[i].v;
if(!vis[v]){
bool flag=dfs(v,!f);
if(!flag) return 0;
}
else if(color[v]==color[u]) return 0;
}
return 1;
}
bool check(int mid){
init();
for(int i=1;i<=m;++i)
if(node[i].w>mid) add(node[i].u,node[i].v),add(node[i].v,node[i].u);
memset(color,0,sizeof(color));
memset(vis,0,sizeof(vis));
bool f=1;
for(int i=1;i<=n;++i)
if(!vis[i]) f&=dfs(i,1);
return f;
}
int main(){
int u,v,w;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i){
scanf("%d%d%d",&u,&v,&w);
node[i].u=u;
node[i].v=v;
node[i].w=w;
}
int l=0,r=1000000000,mid;
while(l<=r){
mid=(l+r)>>1;
if(check(mid)) r=mid-1;
else l=mid+1;
}
printf("%d\n",l);
return 0;
}
二、372. 棋盘覆盖
一张牌可以横着覆盖也可以竖着覆盖,这两个被覆盖的点可以理解为两个不同的集合连了一条边。而且一个格子肯定不可能被两张牌覆盖,于是可以将矩阵压缩成一维,暴力连边,跑匈牙利。
#include
using namespace std;
const int maxm=1e5+7;
const int maxn=1e4+7;
struct Edge{
int v,next;
}edge[maxm<<1];
int head[maxn],top;
void init(){
memset(head,-1,sizeof(head));
top=0;
}
void add(int u,int v){
edge[top].v=v;
edge[top].next=head[u];
head[u]=top++;
}
int n,m;
int match[maxn];
bool vis[maxn];
bool dfs(int u){
int v;
for(int i=head[u];i!=-1;i=edge[i].next){
v=edge[i].v;
if(!vis[v]){
vis[v]=1;
if(match[v]==-1||dfs(match[v])){
match[v]=u;
return 1;
}
}
}
return 0;
}
int dx[4]={0,0,-1,1};
int dy[4]={-1,1,0,0};
bool hh[109][109];
bool check(int i,int j){
if(i>=0&&i=0&&j
三、373. 車的放置
一个车只能覆盖一行一列,一个行列也只能放一个车,所以可以将行与列分为A,B集合,建图跑匈牙利即可。
#include
using namespace std;
const int maxn=409;
struct Edge{
int v,next;
}edge[maxn*maxn*2];
int head[maxn],top;
void init(){
memset(head,-1,sizeof(head));
top=0;
}
void add(int u,int v){
edge[top].v=v;
edge[top].next=head[u];
head[u]=top++;
}
int match[maxn];
bool vis[maxn];
bool hh[maxn][maxn];
bool dfs(int u){
int v;
for(int i=head[u];i!=-1;i=edge[i].next){
v=edge[i].v;
if(!vis[v]){
vis[v]=1;
if(!match[v]||dfs(match[v])){
match[v]=u;
return 1;
}
}
}
return 0;
}
int main(){
int u,v,w;
init();
int n,m,k;
scanf("%d%d%d",&n,&m,&k);
while(k--){
scanf("%d%d",&u,&v);
hh[u][v]=1;
}
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
if(!hh[i][j]) add(i,j+n),add(j+n,i);
int res=0;
for(int i=1;i<=n;++i){
memset(vis,0,sizeof(vis));
if(dfs(i)) ++res;
}
printf("%d\n",res);
return 0;
}
四、374. 导弹防御塔
显然时间具有单调性,可以二分,然后就可以计算出来这个时间内能发射P颗导弹,一个塔可以在这个时间内射出若干颗导弹,那么可以将一个塔拆成P个不同的点,依次将m个入侵者看能不能与P个点连边,建图跑匈牙利判断即可。
#include
using namespace std;
const double epos=1.0e-7;
const int maxn=1e5+7;
int head[maxn],top;
void init(){
top=0;
memset(head,-1,sizeof(head));
}
struct Edge{
int v,w,next;
}edge[maxn<<4];
void add(int u,int v,int w){
edge[top].v=v;
edge[top].w=w;
edge[top].next=head[u];
head[u]=top++;
}
int match[maxn];
bool vis[maxn];
bool dfs(int u){
int v;
for(int i=head[u];i!=-1;i=edge[i].next){
v=edge[i].v;
if(!vis[v]){
vis[v]=1;
if(!match[v]||dfs(match[v])){
match[v]=u;
return 1;
}
}
}
return 0;
}
double t1,t2,v;
int n,m;
pair ru[59],ta[59];
double juli(double x1,double y1,double x2,double y2){
return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
double ju[59][59];
bool check(double mid){
init();
if(midmid) break;
if(k>50) break;
add(i,id,1);
add(id,i,1);
++id;
need+=t1+t2;
}
}
memset(match,0,sizeof(match));
int res=0;
for(int i=1;i<=m;++i){
memset(vis,0,sizeof(vis));
if(!dfs(i)) return 0;
}
return 1;
}
int main(){
scanf("%d%d%lf%lf%lf",&n,&m,&t1,&t2,&v);
t1/=60;
for(int i=1;i<=m;++i)
scanf("%d%d",&ru[i].first,&ru[i].second);
for(int i=1;i<=n;++i)
scanf("%d%d",&ta[i].first,&ta[i].second);
for(int i=1;i<=m;++i)
for(int j=1;j<=n;++j) ju[i][j]=juli(ru[i].first,ru[i].second,ta[j].first,ta[j].second);
double l=0,r=300000,mid;
while(r-l>=epos){
mid=(l+r)/2;
if(check(mid)) r=mid;
else l=mid;
}
printf("%.6f\n",r);
return 0;
}
二分图带权匹配
KM算法只适用于满足完备匹配的图中才能求解,就是两个集合各有n个点,最终有n条匹配边。
五、poj 3565
二维平面上有2*n个点,n个白点,n个黑点,要求每个白点连接一个黑点,使得最后所有的线段都不相交,数据保证有解。
所有线段都不相交,就是使得一个白点连接与他最近的黑点。
那么将边权取成负数后,就转化为求带权最大匹配了。
#include
using namespace std;
const int maxn=109;
double W[maxn][maxn];
double la[maxn],lb[maxn];
bool va[maxn],vb[maxn];
int match[maxn];
int n;
double delta;
double upd[maxn];
const double epos=1.0e-8;
bool dfs(int u){
va[u]=1;
for(int v=1;v<=n;++v){
if(!vb[v])
if(fabs(la[u]+lb[v]-W[u][v])<=epos){
vb[v]=1;
if(!match[v]||dfs(match[v])){
match[v]=u;
return 1;
}
}
else upd[v]=min(upd[v],la[u]+lb[v]-W[u][v]);
}
return 0;
}
double KM(){
for(int i=1;i<=n;++i){
la[i]=-1e10;
lb[i]=0;
for(int j=1;j<=n;++j)
la[i]=max(la[i],W[i][j]);
}
for(int i=1;i<=n;++i)
while(1){
memset(va,0,sizeof(va));
memset(vb,0,sizeof(vb));
delta=1e10;
for (int j = 1; j <= n; j++) upd[j] = 1e10;
if(dfs(i)) break;
for(int j=1;j<=n;++j)
if(!vb[j]) delta=min(delta,upd[j]);
for(int j=1;j<=n;++j){
if(va[j]) la[j]-=delta;
if(vb[j]) lb[j]+=delta;
}
}
double ans=0;
for(int i=1;i<=n;++i) ans+=W[match[i]][i];
return ans;
}
pair A[maxn],B[maxn];
double juli(double x1,double y1,double x2,double y2){
return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
int res[maxn];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d%d",&A[i].first,&A[i].second);
for(int i=1;i<=n;++i) scanf("%d%d",&B[i].first,&B[i].second);
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
W[i][j]=-juli(A[i].first,A[i].second,B[j].first,B[j].second);
KM();
for(int i=1;i<=n;++i) res[match[i]]=i;
for(int i=1;i<=n;++i) printf("%d\n",res[i]);
return 0;
}