高一下三调模拟赛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/05
笔记·图论
£suPerpanda2 天前
牛客周赛 Round65 补题DEF
开发语言·数据结构·c++·算法·深度优先·动态规划·图论
c沫栀3 天前
E-小H学历史(牛客练习赛131)
c++·算法·深度优先·图论
小冉在学习3 天前
day50 图论章节刷题Part02(99.岛屿数量 深搜、99.岛屿数量 广搜、100.岛屿的最大面积)
图论
weixin_478689763 天前
【图论】——理论基础总结
图论
夜雨翦春韭3 天前
【代码随想录Day60】图论Part11
java·数据结构·算法·leetcode·图论
夜雨翦春韭4 天前
【代码随想录Day52】图论Part04
java·数据结构·算法·leetcode·图论
X BoHang5 天前
10.31模拟赛题解
算法·图论
夜雨翦春韭5 天前
【代码随想录Day59】图论Part10
java·开发语言·数据结构·算法·leetcode·图论
deephub5 天前
基于图论的时间序列数据平稳性与连通性分析:利用图形、数学和 Python 揭示时间序列数据中的隐藏模式
人工智能·机器学习·图论·时间序列·1024程序员节