高一下三调模拟赛5.13(附关于二分图匈牙利建边的详细思考)

前言注:本篇为知识性内容,A题附详解关于匈牙利算法求最大独立子集难以理解的建边问题的思考,若有不当之处感谢指出。暂时只写了A篇题解,以供帮助大家理解相关问题,剩余题解会进行补充。

又是小集训的一周,总要伴随着模拟赛...

还是五道题目:

A. 攻击装置

B. 循环

C. 漫步

D. 穿越

E. 结队

赛时拿了230...太菜了,本来预料中ABC全 $ AC $ 的,结果出乎意料地windows + dev给了我当头一棒,我不管,就是他俩的锅, 谁让dev开了O2也不给我报错, 导致我C题

(重载运算符operator 前没加bool类型)

  • A. 攻击装置

赛前huge就已经告诉过我们考题有二分图,再看到这个题"装置互不攻击",明显了,直接开打,但因为建边问题调了一个小时才搓出来,有点慢了。可能昨天做的二分图专题对于这种求最大独立子集为什么建双边不是很明白,这个题虽说用时长了点,但却让我明白了建双边的意义。就这个题叙述一下。

建边操作如下:

cpp 复制代码
void con(int i, int j){
	int now = a[i][j];
	if(a[i-1][j-2]) addedge(now, a[i-1][j-2]);
	if(a[i-2][j-1]) addedge(now, a[i-2][j-1]);
	if(a[i+1][j-2]) addedge(now, a[i+1][j-2]);
	if(a[i+2][j-1]) addedge(now, a[i+2][j-1]);
	if(a[i-1][j+2]) addedge(now, a[i-1][j+2]);
	if(a[i-2][j+1]) addedge(now, a[i-2][j+1]);
	if(a[i+1][j+2]) addedge(now, a[i+1][j+2]);
	if(a[i+2][j+1]) addedge(now, a[i+2][j+1]);
}

即对于每一个点,将其所可以攻击到的点建上单向边,不过当然,now点遍历到现在连向的点后,还会再连回来一条边。

赛时就一直在想能不能只建单向边,
像这样:

cpp 复制代码
void con(int i, int j){
	int now = a[i][j];
	if(a[i-1][j-2]) addedge(now, a[i-1][j-2]);
	if(a[i-2][j-1]) addedge(now, a[i-2][j-1]);
	if(a[i+1][j-2]) addedge(now, a[i+1][j-2]);
	if(a[i+2][j-1]) addedge(now, a[i+2][j-1]);
	// if(a[i-1][j+2]) addedge(now, a[i-1][j+2]);
	// if(a[i-2][j+1]) addedge(now, a[i-2][j+1]);
	// if(a[i+1][j+2]) addedge(now, a[i+1][j+2]);
	// if(a[i+2][j+1]) addedge(now, a[i+2][j+1]);
}

即对于每一个点,我们只连它到下方可攻击到的点:

如图红色为now点可攻击点,但我只连它下方的。

赛时就这么想,可是调半天怎么样也不对,然后思考...突然想明白,

因为这是二分图,我们知道,二分图中点分为两部分,匈牙利算法是把第一部分点连向第二部分,假设我now点为二分图中的第一部分,那么它所攻击的8个点都是第二部分,那么同时这8个点所分别能攻击的64个点也应该是第一部分点。这样的话,按照二分图匈牙利算法 建单向边的思路,我需要把这64个点连向上述的那8个点,那么想要这样做的话,把图中的点分为两部分,保证我所有的边都是有第一部分点连向第二部分,这样一来就会很麻烦。

所以呢,我们就有了建双向边的操作。让第一部分点连向第二部分,同时也让第二部分点反向连回第一部分。那么这样我们可以理解为把所有点分为A, B $ 两个部分 ,并建了两个二分图,一个是`A`作第一部分,其中的点连向`B`部分中的点,另一个则相反,是`B`中的点连向`A`中的点。举个例子: 假定 A 部分有编号为 1,3,4 的三个点, B 部分有 2,5,6,7 $四个点,两部分相对应的点建双向边,如图:

我们理解为形成了这样两个二分图,那么跑匈牙利算法的时候等同于又把这两个二分图合二为一,得到了总二分图的最小点覆盖数,且显然两个二分图的最小点覆盖值是相等的,所以我们想要的符合题目的最小点覆盖数即为匈牙利所求的一半,所以最后输出的时候要$ tot-Hun()/2 $

代码如下:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

const int N = 4e4 + 10;
const int M = 1e7 + 10;

int n, a[210][210], tot;
bool flag[N];
int match[N]; bool vis[N];

int head[M], to[M], nxt[M], cnt;
void addedge(int x, int y){
	flag[x] = flag[y] = true;
	to[++cnt] = y;
	nxt[cnt] = head[x];
	head[x] = cnt;
}

bool find(int x){
	for(int i=head[x]; i; i=nxt[i]){
		int y = to[i];
		if(!vis[y]){
			vis[y] = true;
			
			if(!match[y] or find(match[y])){
				match[y] = x;
				return true;	
			}
		}
	}
	return false;
}

int Hun(){
	int ans = 0;
	for(int i=1; i<=tot; i++){
		memset(vis, 0, sizeof(vis));
		if(find(i)) ans++;
	}
	return ans;
}

void con(int i, int j){
	int now = a[i][j];
	if(a[i-1][j-2]) addedge(now, a[i-1][j-2]);
	if(a[i-2][j-1]) addedge(now, a[i-2][j-1]);
	if(a[i+1][j-2]) addedge(now, a[i+1][j-2]);
	if(a[i+2][j-1]) addedge(now, a[i+2][j-1]);
	if(a[i-1][j+2]) addedge(now, a[i-1][j+2]);
	if(a[i-2][j+1]) addedge(now, a[i-2][j+1]);
	if(a[i+1][j+2]) addedge(now, a[i+1][j+2]);
	if(a[i+2][j+1]) addedge(now, a[i+2][j+1]);
}

int main(){
	freopen("attack.in", "r", stdin);
	freopen("attack.out", "w", stdout);
	
	scanf("%d", &n);
	for(int i=1; i<=n; i++){
		for(int j=1; j<=n; j++){
			char in; cin>>in;
			if(in == '0') a[i][j] = ++tot;
		}
	}
	for(int i=1; i<=n; i++){
		for(int j=1; j<=n; j++){
			if(a[i][j])	con(i, j);
		}
	}
	
	printf("%d", tot - Hun() / 2);
	
	return 0;
}
  • B. 循环
  • C. 漫步
  • D. 穿越
  • E. 结队
相关推荐
chan_lay2 小时前
图论导引 - 第二章 - 11/08
图论
小冉在学习2 小时前
day55 图论章节刷题Part07([53.寻宝]prim算法、kruskal算法)
java·算法·图论
chan_lay3 小时前
图论导引 - 第三章 第一节:连通性 - 11/09
图论
小冉在学习7 小时前
day57 图论章节刷题Part08(拓扑排序、dijkstra(朴素版))
java·算法·图论
Fool25635316 小时前
Guarding the Chessboard(UVA 11214)
算法·深度优先·图论
JingHongB2 天前
代码随想录算法训练营Day55 | 图论理论基础、深度优先搜索理论基础、卡玛网 98.所有可达路径、797. 所有可能的路径、广度优先搜索理论基础
算法·深度优先·图论
weixin_432702262 天前
代码随想录算法训练营第五十五天|图论理论基础
数据结构·python·算法·深度优先·图论
小冉在学习2 天前
day52 图论章节刷题Part04(110.字符串接龙、105.有向图的完全可达性、106.岛屿的周长 )
算法·深度优先·图论
小冉在学习2 天前
day53 图论章节刷题Part05(并查集理论基础、寻找存在的路径)
java·算法·图论
chan_lay4 天前
图论导引 - 目录、引言、第一章 - 11/05
笔记·图论