二分图最大独立集
定义
对于一张无向图
求出一个点数最大的点集
使得点集中任意两点没有边相连
这是图的最大独立集
二分图的最大独立集,就是字面意思
解法
对于一个有n个结点的二分图
它的最大独立集包含的点数就是n-最大匹配数
证明
转换一下思路,上述问题等价于
在二分图中去掉最少数量的点,使得剩余的点两两没有连边
我们假设去掉一个点的同时去掉所有与这个点的连边
那么去掉一个图的覆盖集后剩余的点两两必定没有连边,即得到了一个独立集
那么最大独立集就是 点的总数-最小覆盖集
即二分图中 n-最大匹配数
二分图最大点权独立集
定义
要求点权和最大的二分图独立集
解法
超源向所有x部的点连边,容量为该点点权
y部所有点向超汇连边,容量为该点点权
对于二分图原有的边,容量为inf
然后总点权-最大流即为所求
洛谷 P4304[Tjoi2013]攻击装置
Description
给定一个01矩阵,其中你可以在0的位置放置攻击装置。每一个攻击装置(x,y)都可以按照“日”字攻击其周围的 8个位置(x-1,y-2),(x-2,y-1),(x+1,y-2),(x+2,y-1),(x-1,y+2),(x-2,y+1), (x+1,y+2),(x+2,y+1)
求在装置互不攻击的情况下,最多可以放置多少个装置。
Input
第一行一个整数N,表示矩阵大小为N*N。接下来N行每一行一个长度N的01串,表示矩阵。
Output
一个整数,表示在装置互不攻击的情况下最多可以放置多少个装置。
分析
将矩阵黑白染色,让相邻两格颜色不同
(设奇数格为黑,偶数格为白)
每个黑点向其走一个"日"就能到达的点连边
显然这些连出去的边都指向白点
那么这样得到的图一定是个二分图
这时我们发现题目所求就是这个二分图的最大独立集
#include<iostream>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long lt;
int read()
{
int f=1,x=0;
char ss=getchar();
while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
return f*x;
}
const int inf=1e9;
const int maxn=50010;
int n,m,s,t;
struct node{int v,f,nxt;}E[maxn*100];
int head[maxn],tot=1;
int rem[210][210];
int lev[maxn],cur[maxn],maxf;
int addx[]={1,1,-1,-1,2,2,-2,-2};
int addy[]={2,-2,2,-2,1,-1,1,-1};
char ss[210];
void add(int u,int v,int f)
{
E[++tot].nxt=head[u];
E[tot].v=v; E[tot].f=f;
head[u]=tot;
}
int bfs()
{
queue<int> q; q.push(s);
memset(lev,-1,sizeof(lev)); lev[s]=0;
while(!q.empty())
{
int u=q.front(); q.pop();
for(int i=head[u];i;i=E[i].nxt)
{
int v=E[i].v;
if(lev[v]==-1&&E[i].f)
{
lev[v]=lev[u]+1;
if(v==t) return 1;
q.push(v);
}
}
}
return 0;
}
int dfs(int u,int cap)
{
if(u==t||!cap) return cap;
int flow=0;
for(int i=cur[u];i;i=E[i].nxt)
{
int v=E[i].v;
if(lev[v]==lev[u]+1&&E[i].f)
{
int f=dfs(v,min(E[i].f,cap-flow));
E[i].f-=f; E[i^1].f+=f; flow+=f;
if(f) cur[u]=i;
if(flow==cap) return cap;
}
}
if(!flow) lev[u]=-1;
return flow;
}
int main()
{
n=read();t=n*n+1;
for(int i=1;i<=n;++i)
{
scanf("%s",&ss);
for(int j=0;j<n;++j)
{
rem[i][j+1]=ss[j]-'0';
if(ss[j]-'0'==1) m++;
}
}
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
{
if(rem[i][j]) continue;
int num=(i-1)*n+j;
if( !((i+j)&1) )
{
add(s,num,1); add(num,s,0);
for(int k=0;k<8;++k)
{
int x=i+addx[k],y=j+addy[k];
if(x<1||x>n||y<1||y>n||rem[x][y]) continue;
int num1=(x-1)*n+y;
add(num,num1,inf); add(num1,num,0);
}
}
else add(num,t,1),add(t,num,0);
}
while(bfs())
{
for(int i=0;i<=n*n+1;++i) cur[i]=head[i];
maxf+=dfs(s,inf);
}
printf("%d",n*n-m-maxf);
return 0;
}
洛谷 P2774 方格取数问题
题目描述
在一个有 m*n 个方格的棋盘中,每个方格中有一个正整数。现要从方格中取数,使任意 2 个数所在方格没有公共边,且取出的数的总和最大。试设计一个满足要求的取数算法。对于给定的方格棋盘,按照取数要求编程找出总和最大的数。
输入格式:
第 1 行有 2 个正整数 m 和 n,分别表示棋盘的行数和列数。接下来的 m 行,每行有 n 个正整数,表示棋盘方格中的数。
说明
m,n<=100
分析
同样将格子黑白染色
我们由黑点向所有与他相邻的白点连边
就构成了一个二分图
到这里我们就会发现题目所求就是这个二分图的最大点权独立集
用上述方法解决即可
所有黑点向超汇连边,容量为该点点权
超源向所有白点连边,容量为该点点权
对于上面所述用于构成二分图的边,容量为inf
然后跑最大流就好
#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
int read()
{
int f=1,x=0;
char ss=getchar();
while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
return f*x;
}
const int inf=1e9;
const int maxn=20010;
int n,m;
int s=0,t;
int col[110][110];
struct node{int v,f,nxt;}E[maxn*100];
int head[maxn],tot=1;
int lev[maxn],sum,maxf;
int addx[]={0,0,1,-1};
int addy[]={1,-1,0,0};
void add(int u,int v,int f)
{
E[++tot].nxt=head[u];
E[tot].f=f; E[tot].v=v;
head[u]=tot;
}
int bfs()
{
queue<int> q; q.push(s);
memset(lev,-1,sizeof(lev)); lev[s]=0;
while(!q.empty())
{
int u=q.front(); q.pop();
for(int i=head[u];i;i=E[i].nxt)
{
int v=E[i].v;
if(lev[v]==-1&&E[i].f)
{
lev[v]=lev[u]+1;
if(v==t) return 1;
q.push(v);
}
}
}
return 0;
}
int dfs(int u,int cap)
{
if(u==t) return cap;
int flow=cap;
for(int i=head[u];i;i=E[i].nxt)
{
int v=E[i].v;
if(lev[v]==lev[u]+1&&E[i].f>0&&flow)
{
int f=dfs(v,min(E[i].f,flow));
flow-=f;
E[i].f-=f; E[i^1].f+=f;
}
}
return cap-flow;
}
int main()
{
n=read();m=read(); t=n*m+1;
for(int i=1;i<=n;i++)//构图
for(int j=1;j<=m;j++)
{
int val=read(); sum+=val;
int num=m*(i-1)+j;//计算该点的编号
if( !((i+j)&1) )
{
add(s,num,val);add(num,s,0);//超源向白点连边
for(int k=0;k<4;k++)//原二分图中的边
{
int x=i+addx[k],y=j+addy[k];
int num1=m*(x-1)+y;
if(x<1||x>n||y<1||y>m) continue;
add(num,num1,inf); add(num1,num,0);
}
}
else add(num,t,val),add(t,num,0);//黑点向超汇连边
}
while(bfs())
maxf+=dfs(s,inf);
printf("%d",sum-maxf);
return 0;
}