「TJOI2013」 攻击装置 - 二分图最大独立集

题目描述

给定一个01矩阵,其中你可以在0的位置放置攻击装置。 每一个攻击装置(x,y)(x,y)(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)(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)(x1,y2),(x2,y1),(x+1,y2),(x+2,y1),(x1,y+2),(x2,y+1),(x+1,y+2),(x+2,y+1)

求在装置互不攻击的情况下,最多可以放置多少个装置。

输入格式

第一行一个整数NNN,表示矩阵大小为N∗NN*NNN

接下来NNN行每一行一个长度NNN的01串,表示矩阵。

输出格式

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

数据范围

30%30\%30%数据N&lt;=50N&lt;=50N<=50

100%100\%100%数据N&lt;=200N&lt;=200N<=200

分析

跟网络流24题中骑士共存问题一样,每个格子按横纵坐标之和的奇偶性分为黑点和白点,能够到达的两点之间,由黑点向白点建边,之后就是求这个二分图的最大独立集。有定理:二分图中最大独立集=总点数-二分图最小点覆盖=总点数-二分图最大匹配数,所以用匈牙利和Dinic都可以。但是为了放置出题人卡匈牙利,还是用Dinic更保险一些。用Dinic则需要从源点向二分图的左部节点建边,流量为1,由左部向右部建的边流量为1,再由右部节点向汇点建边,流量为1,这样源点到汇点的最大流即为该二分图的最大匹配数。

代码

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
using namespace std;
const int N=205,M=N*N*N+10;
const int NN=N*N,Inf=0x7fffffff/2;
struct Edge {
	int to,nxt,flow;
}e[M];
int h[NN],cnt,tot;
int n1[NN],n2[NN];
int tot1,tot2;
int n,ans,m,g[N][N],f[NN];
char s[N][N];
bool vis[NN];
int ntp[8][2]={{1,2},{1,-2},{-1,2},{-1,-2},{2,1},{2,-1},{-2,1},{-2,-1}};
void Add(int x,int y,int z) {
	e[++cnt]=(Edge){y,h[x],z};
	h[x]=cnt;
}
struct Dinic {
	int dis[NN];
	bool Bfs(int s,int t) {
		memset(dis,0,sizeof(dis));
		queue<int> q;
		q.push(s);
		dis[s]=1;
		while (!q.empty()) {
			int x=q.front();
			q.pop();
			for (int i=h[x];i;i=e[i].nxt) {
				int y=e[i].to;
				if (e[i].flow&&!dis[y]) {
					q.push(y);
					dis[y]=dis[x]+1;
					if (y==t) return 1;
				}
			}
		}
		return 0;
	}
	int Dfs(int x,int flow,int t) {
		if (x==t) return flow;
		int rest=flow,k;
		for (int i=h[x];i&&rest;i=e[i].nxt) {
			int y=e[i].to;
			if (e[i].flow&&dis[y]==dis[x]+1) {
				k=Dfs(y,min(rest,e[i].flow),t);
				if (!k) dis[y]=0;
				e[i].flow-=k;
				e[i^1].flow+=k;
				rest-=k;
			}
		}
		return flow-rest;
	}
	int maxflow(int s,int t) {
		int flow=0,ans=0;
		while (Bfs(s,t))
			while ((flow=Dfs(s,Inf,t))) ans+=flow;
		return ans;
	}
}dinic;
int main() {
	scanf("%d",&n);
	for (int i=1;i<=n;i++) {
		scanf("%s",s[i]+1);
	}
	cnt=1;
	for (int i=1;i<=n;i++)
		for (int j=1;j<=n;j++) {
			g[i][j]=++tot;
			if (s[i][j]=='1') {
				m++;
				continue;
			}
			if (!((i+j)&1)) n1[++tot1]=g[i][j];
			else n2[++tot2]=g[i][j];
		}
	for (int i=1;i<=n;i++) {
		for (int j=((i&1)?1:2);j<=n;j+=2) {
			if (s[i][j]=='1') continue;
			for (int k=0;k<8;k++) {
				int tx,ty;
				tx=i+ntp[k][0];
				ty=j+ntp[k][1];
				if (tx<1||tx>n||ty<1||ty>n) continue;
				if (s[tx][ty]=='1') continue;
				Add(g[i][j],g[tx][ty],1);
				Add(g[tx][ty],g[i][j],0);
			}
		}
	}
	for (int i=1;i<=tot1;i++) {
		Add(0,n1[i],1);
		Add(n1[i],0,0);
	}
	for (int i=1;i<=tot2;i++) {
		Add(n2[i],tot+1,1);
		Add(tot+1,n2[i],0);
	}
	printf("%d",n*n-m-dinic.maxflow(0,tot+1));
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值