共42道题,当前是第25

初赛真题

给一个矩阵 $N * M$ ,给你第 $i$ 行 $1$ 的个数和位置,让你选些行精确覆盖 $M$ 列(精确覆盖:每列有且只有 $1$ 个 $1$)。 例如:如下的矩阵:


11100001
10001110
10010110
00010010
00001100

就包含了这样一个集合(第 `1,4,5` 行)。

Input
多组数据,对于每组数据:
第一行两个整数 $N,M$ ;
接下来 $N$ 行,每行开头一个整数 $x$ ,表示该行上 $1$ 的个数,接下来 $x$ 个整数,每个 $1$ 的位置。

Output
如果有解随意输出组集合,否则输出 `NO` ,中间空格隔开。
提示:一个典型的跳舞链问题。跳舞链是一个比较古老的算法,这里梳理一下这道题的大致
思路:
- 如果当前矩阵只有一行:
- 如果全都是 ,说明已经找到了一种合法选法。
- 否则选法是不合法的,需要回溯。
- 枚举其中的一行,选择它。
- 删除掉矩阵中和被选择的这一行在相同列有 $1$ 的行。
- 删除掉被选择的这一行有 $1$ 的列。
- 递归向下继续操作。

- 如果没找到答案,把已删除的部分恢复。

#include<bits/stdc++.h>
using namespace std;
# define ll long long
# define inf 10000000000
# define N 2005
# define M 2000005
int read() {
	int x = 0, f = 1;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		if (ch == '-') f = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + ch - '0';
		ch = getchar();
	}
	return x * f;
}

int n, m;
int h[N], s[N], q[N];
int u[M], d[M], L[M], R[M], C[M], X[M];
void del(int c){ //delete ROW C
	___(1)___;
	___(2)___;
	for (int i = d[c]; i != c; i = d[i])
		for (int j = R[i]; j != i; j = R[j])
			u[d[j]] = u[j], d[u[j]] = d[j], s[C[j]]--;
}

void add(int c) {
	L[R[c]] = R[L[c]] = c;
	for (int i = u[c]; i != c; i = u[i])
		for (int j = L[i]; j != i; j = L[j])
			u[d[j]] = d[u[j]] = j, s[C[j]]++;
}
void link(int r, int c) {
	static int size = 0;
	size++;
	X[size] = r;
	C[size] = c;
	s[c]++;
	d[size] = d[c];
	u[size] = c;
	u[d[size]] = size;
	d[u[size]] = size;
	if (h[r] == -1)
		h[r] = L[size] = R[size] = size;
	else {
		R[size] = R[h[r]];
		L[size] = h[r];
		L[R[size]] = size;
		R[L[size]] = size;
	}
}

bool dance(int k) {
	if (R[0] = 0) {
		printf("%d", k);
		for (int i = 1; i <= k; i++)
		printf(" %d", X[q[i]]);
		puts("");
		return 1;
	}
	int mn = inf, c;
	for (int i = R[0]; i; i = R[i])
		if (s[i] < mn) mn = s[i], c = i;
	___(3)___;
	for (int i = d[c]; i != c; i = d[i]) {
		q[k + 1] = i;
		for (int j = R[i]; j != i; j = R[j]) ___(4)___;
		if (dance(k + 1)) return 1;
		for (int j = L[i]; j != i; j = L[j]) ___(5)___;
	}
	add(c);
	return 0;
}
int main() {
	while (scanf("%d%d", & n, & m) != EOF) {
		for (int i = 0; i <= m; i++) {
			d[i] = u[i] = i;
			L[i + 1] = i;
			R[i] = i + 1;
			s[i] = 0;
		}
		R[m] = 0;
		size = m;
		int x, y;
		for (int i = 1; i <= n; i++) {
			h[i] = -1;
			x = read();
			while (x--) {
				y = read();
				link(i, y);
			}
		}
		if (!dance(0)) puts("NO");
	}
	return 0;
}

1. A。
2. C。
3. C。
4. C。
5. A。

这段代码是经典的跳舞链,总的来说就是通过维护双向链表来动态解决行列的删除和恢复,每选择一行后就根据列上的 $1$ 将问题简化到更小数据规模的问题,以此来进行递归处理。更多的详细介绍的代码思路可以参考博客: https://www.cnblogs.com/grenet/p/3145800.html。

Question

1. (1) 处应填( )

2. (2) 处应填( )

3. (3) 处应填( )

4. (4) 处应填( )

5. (5) 处应填( )

陈伦制作 版权所无 粤ICP备16127491号-1