高一下三调模拟赛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. 结队
相关推荐
散1124 小时前
01数据结构-Prim算法
数据结构·算法·图论
KyollBM8 小时前
【图论】分层图 / 拆点
图论
GawynKing11 小时前
图论(5)最小生成树算法
算法·图论·最小生成树
KarrySmile13 小时前
Day60--图论--94. 城市间货物运输 I(卡码网),95. 城市间货物运输 II(卡码网),96. 城市间货物运输 III(卡码网)
图论·spfa·bellman_ford·队列优化·最短路算法·负权回路·单源有限最短路
花开富贵ii17 小时前
代码随想录算法训练营四十三天|图论part01
java·数据结构·算法·深度优先·图论
yi.Ist1 天前
图论——Djikstra最短路
数据结构·学习·算法·图论·好难
KarrySmile1 天前
Day55--图论--107. 寻找存在的路径(卡码网)
图论·并查集·寻找存在的路径
KarrySmile2 天前
Day62--图论--97. 小明逛公园(卡码网),127. 骑士的攻击(卡码网)
图论·floyd·floyd算法·弗洛伊德算法·astar算法·小明逛公园·骑士的攻击
Morriser莫3 天前
图论Day2学习心得
算法·图论
KarrySmile3 天前
Day53--图论--106. 岛屿的周长(卡码网),110. 字符串接龙(卡码网),105. 有向图的完全联通(卡码网)
深度优先·图论·广度优先·广搜·岛屿的周长·字符串接龙·有向图的完全联通