二分图 x 独立集 洛谷P2774 方格取数问题 P4304 [TJOI2013]攻击装置

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/niiick/article/details/82973395

二分图最大独立集

定义

对于一张无向图
求出一个点数最大的点集
使得点集中任意两点没有边相连
这是图的最大独立集

二分图的最大独立集,就是字面意思

解法

对于一个有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

一个整数,表示在装置互不攻击的情况下最多可以放置多少个装置。


分析

将矩阵黑白染色,让相邻两格颜色不同
(设奇数格为黑,偶数格为白)

扫描二维码关注公众号,回复: 3601725 查看本文章

每个黑点向其走一个"日"就能到达的点连边
显然这些连出去的边都指向白点
那么这样得到的图一定是个二分图

这时我们发现题目所求就是这个二分图的最大独立集


#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;
}

猜你喜欢

转载自blog.csdn.net/niiick/article/details/82973395