FZU 1686 神龙的难题 舞蹈链重复覆盖问题,个人理解和做法

本文介绍如何使用舞蹈链算法解决最小攻击次数问题,通过将敌人位置转换为列,攻击范围转换为行,实现对敌人全部消灭的最小攻击次数计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:http://acm.fzu.edu.cn/problem.php?pid=1686

(不知道为什么每次做舞蹈链的题目就会莫名有要找很久的bug)

首先重复舞蹈链问题的解法就是最少的行的集合使得集合里的所有数字能覆盖一整行。做法是差不多不会变的了,我们需要将这些题转化为这个问题的模型。

首先舞蹈链中的列需要都被覆盖,对应的就是问题里的敌人被消灭。

所以对于有敌人的地方需要将他们看作列,没敌人的地方不能放到列上面去,那么要怎么放呢。

我们再看舞蹈链中的行,我们是需要从所有的行里面选择出加起来能覆盖一整行的行的集合,因此这里的行更多代表的是所有情况。也就是我们需要将所有攻击的情况放到行里面去。一行对应一次攻击,那么行的次数对应的也就是攻击的次数

攻击一次范围为a*b格,我们可以取左上的那点代表此次攻击,假如题目是n行m列的话,那么舞蹈链中就一共有 n-a+1 * m-b+1 行

对于同一次攻击,我们将其能攻击到的所有敌人都表示为舞蹈链中该行的1就可以了。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn1 = 17*17;
const int maxn2 = maxn1 * maxn1;
int M[maxn1][maxn1], up[maxn2], dow[maxn2], lef[maxn2], rig[maxn2], col[maxn2], row[maxn2];
//col : 每一点的列坐标   row: 每一点的横坐标
int hav[maxn2], head[maxn2];// hav:每列有多少个元素   head:每行的行头
int ans, id,flag;
void init() {
	memset(head, -1, sizeof(head));
	memset(hav, 0, sizeof(hav));
	for (int i = 0; i <= id; i++) {
		lef[i] = i - 1;
		rig[i] = i + 1;
		up[i] = dow[i] = i;
	}
	lef[0] = id;
	rig[id] = 0;
}
void Connect(int x, int y) {
	row[++id] = x;  col[id] = y;
	up[id] = up[y]; 
	dow[up[y]] = id;
	dow[id] = y;
	up[y] = id;
	if (head[x] == -1) head[x] = lef[id] = rig[id] = id;
	else {
		lef[id] = lef[head[x]];
		rig[lef[head[x]]] = id;
		lef[head[x]] = id;
		rig[id] = head[x];
	}
	hav[y]++;
}
void remove(int aim) { //重复覆盖只需要删除该列就可以了
	for (int i = dow[aim]; i != aim; i = dow[i]) { //从该点到下然后回到上面再回到该点的遍历方式
		lef[rig[i]] = lef[i];
		rig[lef[i]] = rig[i];
	}
}
void resume(int aim) {
	for (int i = up[aim]; i != aim; i = up[i]) {
		lef[rig[i]] = i;
		rig[lef[i]] = i;
	}
}
bool per[maxn2];
int perdict() { //预估函数,通过估计接下来最优的情况来判断是否有必要搜索下去
	int  res = 0;
	for (int i = rig[0]; i != 0; i = rig[i]) per[i] = 0;
	for (int i = rig[0]; i != 0; i = rig[i]) {
		if (!per[i]) {
			per[i] = 1;
			res++;
			for (int j = up[i]; j != i; j = up[j]) {
				for (int k = rig[j]; k != j; k = rig[k]) per[col[k]] = 1;
			}
		}
	}
	return res;
}
void dancing_link(int aim) {
	if (aim + perdict() >= ans) {
		return;
	}
	if (!rig[0]) {
		ans = ans < aim ? ans : aim;
		return;
	}
	int minn = rig[0];
	for (int i = rig[0]; i != 0; i = rig[i]) {
		if (hav[i] < hav[minn]) minn = i;
	}
	for (int i = dow[minn]; i != minn; i = dow[i]) {
		remove(i);
		for (int j = rig[i]; j != i; j = rig[j]) remove(j);
		dancing_link(aim + 1);
		for (int j = lef[i]; j != i; j = lef[j]) resume(j);
		resume(i);
	}
}
int main() {
	int n, m;
	while (cin >> n >> m) {
		id = 0;
		memset(M, 0, sizeof(M));
		for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) {
			int inp; scanf("%d", &inp);
			if (inp) M[i][j] = ++id;  //每个点的id值代表他在舞蹈链中的列数
		}
		int x, y; cin >> x >> y;
		init();
		for (int i = 1; i <= n - x+1; i++) {
			for (int j = 1; j <= m - y+1; j++) {
				for (int xx = i; xx <= i + x-1; xx++) {
					for (int yy = j; yy <= j + y-1; yy++) {
						if (M[xx][yy]) Connect(j + (i - 1)*(m - y + 1), M[xx][yy]);
					}
				}
			}
		}
		ans = 0x3f3f3f3f;
		dancing_link(0);
		cout << ans << endl;
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值