HDU1569 方格取数(2)(最大点权独立集 + 最小点权覆盖集 = 总权和)

给出一个 N * M 的矩阵,每个格放着一个非负数,要求选出一些数,使他们的和最大,要求是有相邻边的格子里的数不能同时选。

先说,我压根没想过这事网络流……因为方格取数(1)是个状态压缩……

看了题解,才明白的:

这个题由于数据范围较大,所以状态压缩过不去,需要用网络流,我重复一遍人家的建图:

我们知道对于普通二分图来说,最大独立点集 + 最小点覆盖集 = 总点数,类似的,对于有权的二分图来说,有:

最大点权独立集 + 最小点权覆盖集 = 总点权和,

这个题很明显是要求 最大点权独立集 ,现在 总点权 已知,我们只要求出来 最小点权覆盖集 就好了,我们可以这样建图,

1,对矩阵中的点进行黑白着色(相邻的点颜色不同),从源点向黑色的点连一条边,权值为该黑色点的权值,

2,从白色的点向汇点连一条边,权值为该白色点的权值,

3,然后,对于每一对相邻的黑白点,从黑点向白点连一条边,权值为无穷大。

最后求最小割(最大流),即为最小点权覆盖集。

因为我们求出的最小割集一定是从那些相邻的黑白点之间的边(也就是不能用的边,因为相邻的数不能同时选)中选出来的,且是最小代价,也就是说从方格中拿掉的数之和尽量小,那么剩下的数之和一定是最大的。

我只能说,神奇的网络流!!!!Orz!!!!

代码:

#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
#define find_min(a,b) a<b?a:b
using namespace std;

const int N = 2550;
const int MAX = 100000;

struct Edge{
    int s,e,v;
    int next;
}edge[20*N];

int dir[4][2]={-1,0, 1,0, 0,-1, 0,1};
int n,m,e_num,head[N],d[N],sp,tp;

void AddEdge(int a,int b,int c){
    edge[e_num].s=a; edge[e_num].e=b; edge[e_num].v=c;
    edge[e_num].next=head[a]; head[a]=e_num++;

    edge[e_num].s=b; edge[e_num].e=a; edge[e_num].v=0;
    edge[e_num].next=head[b]; head[b]=e_num++;
}

int judge(int i,int j,int k){
    int ii=i+dir[k][0];
    int jj=j+dir[k][1];
    if(ii>=1 && ii<=n && jj>=1 && jj<=m)return 1;
    return 0;
}

int bfs(){
	queue <int> q;
	memset(d,-1,sizeof(d));
	d[sp]=0;
	q.push(sp);
	while(!q.empty()){
		int cur=q.front();
		q.pop();
		for(int i=head[cur];i!=-1;i=edge[i].next){
			int u=edge[i].e;
			if(d[u]==-1 && edge[i].v>0){
				d[u]=d[cur]+1;
				q.push(u);
			}
		}
	}
	return d[tp] != -1;
}

int dfs(int a,int b){
	int r=0;
	if(a==tp)return b;
	for(int i=head[a];i!=-1 && r<b;i=edge[i].next){
		int u=edge[i].e;
		if(edge[i].v>0 && d[u]==d[a]+1){
			int x=find_min(edge[i].v,b-r);
			x=dfs(u,x);
			r+=x;
			edge[i].v-=x;
			edge[i^1].v+=x;
		}
	}
	if(!r)d[a]=-2;
	return r;
}

int dinic(int sp,int tp){
	int total=0,t;
	while(bfs()){
		while(t=dfs(sp,MAX))
		total+=t;
	}
	return total;
}

int main(){
    int i,j,k,a;
    while(~scanf("%d%d",&n,&m))
    {
        int sum=0;
        e_num=0;
        memset(head,-1,sizeof(head));
        sp=0; tp=n*m+1;
        for(i=1;i<=n;i++){
            for(j=1;j<=m;j++){
                scanf("%d",&a);
                sum+=a;
                int x=(i-1)*m+j;
                if((i+j)%2==0){
                    AddEdge(sp,x,a);
                    for(k=0;k<4;k++){
                        if(judge(i,j,k)==1){//不出界
                            int y=(i+dir[k][0]-1)*m+(j+dir[k][1]);
                            AddEdge(x,y,MAX);//这里要注意边的方向,是黑点向白点连边
                        }
                    }
                }
                else{
                    AddEdge(x,tp,a);
                    for(k=0;k<4;k++){
                        if(judge(i,j,k)==1){//不出界
                            int y=(i+dir[k][0]-1)*m+(j+dir[k][1]);
                            AddEdge(y,x,MAX);//注意边的方向,和上面的是相反的
                        }
                    }
                }
            }
        }
        int max_flow=dinic(sp,tp);
        printf("%d\n",sum-max_flow);
    }
    return 0;
}



 


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值