378 骑士放置(最大独立集)

1. 问题描述:

给定一个 N×M 的棋盘,有一些格子禁止放棋子。问棋盘上最多能放多少个不能互相攻击的骑士(国际象棋的"骑士",类似于中国象棋的"马",按照"日"字攻击,但没有中国象棋"别马腿"的规则)。

输入格式

第一行包含三个整数 N,M,T,其中 T 表示禁止放置的格子的数量。接下来 T 行每行包含两个整数 x 和 y,表示位于第 x 行第 y 列的格子禁止放置,行列数从 1 开始。

输出格式

输出一个整数表示结果。

数据范围

1 ≤ N,M ≤ 100

输入样例:

2 3 0

输出样例:

4
来源:378. 骑士放置 - AcWing题库

2. 思路分析:

分析题目可以知道我们需要求解在图中最多可以选择多少个点使得选出的点两两是不能够相互攻击,属于最大独立集的问题,最大独立集:从一个图中选出最多的点使得选出的点之间是没有边的,最大团:从一个图中选出若干个点使得任意两点都是有边的,最大独立集与最大团是互补的关系,原图的最大独立集就是补图的最大团;在二分图中求解最大独立集等价于我们需要在图中去掉最少的点使得所有的边都破坏掉,等价于求解最小点覆盖,等价于求解最大匹配,但是还需要验证一下是否是二分图,一个图是二分图等价于染色法不存在矛盾,我们可以将每一个格子看成是一个点,把相互可以攻击的位置连一条边,当前格子可以攻击八个不同的方向("日"字型)可以隔一个格子进行染色,可以发现相互攻击的位置都是不同的颜色,也即染色的过程中不存在矛盾那么是二分图,在二分图中有一个比较重要的结论:最大独立集 = 总点数 - 最小点覆盖 = 总点数 - 最大匹配数,所以我们可以使用匈牙利算法求解最大匹配数即可,最终答案为:n * m - k - res,其中k为坏掉的格子数量,res为最大匹配的数目,因为是二维平面所以match数组的每一个元素要存储匹配的坐标,对于python可以使用声明一个二维列表,列表的每一个元素为元组类型,c++语言可以声明为pair类型。

其实举一个例子就比较好理解了,在2 * 3的矩阵中,坏掉的格子数量为0,那么可以放置最多四个不会相互攻击的骑士,其实是在所有相互攻击的边中去掉最小的点,使得所有的相互攻击的边全部破坏掉,最终这些点就不会相互攻击了,而这些去掉的点的位置是不能够填骑士的,所以要减去这些点的数量:

并且类似于之前的棋盘覆盖的题目我们也是枚举奇数格子或者偶数格子即可,这样相当于是枚举一个方向。

3. 代码如下:

c++:

#include <cstring>
#include <iostream>
#include <algorithm>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 110;

int n, m, k;
PII match[N][N];
bool g[N][N], st[N][N];

int dx[8] = {-2, -1, 1, 2, 2, 1, -1, -2};
int dy[8] = {1, 2, 2, 1, -1, -2, -2, -1};

bool find(int x, int y)
{
    for (int i = 0; i < 8; i ++ )
    {
        int a = x + dx[i], b = y + dy[i];
        if (a < 1 || a > n || b < 1 || b > m) continue;
        if (g[a][b]) continue;
        if (st[a][b]) continue;

        st[a][b] = true;

        PII t = match[a][b];
        if (t.x == 0 || find(t.x, t.y))
        {
            match[a][b] = {x, y};
            return true;
        }
    }

    return false;
}

int main()
{
    cin >> n >> m >> k;

    for (int i = 0; i < k; i ++ )
    {
        int x, y;
        cin >> x >> y;
        g[x][y] = true;
    }

    int res = 0;
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
        {
            if (g[i][j] || (i + j) % 2) continue;
            memset(st, 0, sizeof st);
            if (find(i, j)) res ++ ;
        }

    cout << n * m - k - res << endl;
    return 0;
}

python(最后一个数据爆栈了):

from typing import List
import sys


class Solution:
    # 枚举8个可以攻击到的方向, 画一下图就比较清楚了
    pos = [[-2, -1], [-2, 1], [-1, -2], [-1, 2], [1, -2], [1, 2], [2, 1], [2, -1]]

    def find(self, x: int, y: int, n: int, m: int, st: List[List[int]], match: List[List[tuple]], g: List[List[int]]):
        for i in range(8):
            a, b = x + self.pos[i][0], y + self.pos[i][1]
            # 当前的格子不合法
            if a < 1 or a > n or b < 1 or b > m or st[a][b] == 1 or g[a][b] == 1: continue
            st[a][b] = 1
            if match[a][b][0] == -1 or self.find(match[a][b][0], match[a][b][1], n, m, st, match, g):
                match[a][b] = (x, y)
                return True
        return False

    # 其实画图比较好理解, 画一个2 * 3的矩阵
    def process(self):
        n, m, k = map(int, input().split())
        g = [[0] * (m + 10) for i in range(n + 10)]
        for i in range(k):
            x, y = map(int, input().split())
            g[x][y] = 1
        match = [[(-1, -1)] * (m + 10) for i in range(n + 10)]
        res = 0
        for i in range(1, n + 1):
            for j in range(1, m + 1):
                # 我们只需要枚举奇数格子或者是偶数格子即可, 枚举偶数格子跳过奇数格子, 反过来也是一样的
                if (i + j) % 2 or g[i][j] == 1: continue
                st = [[-1] * (m + 10) for i in range(n + 10)]
                if self.find(i, j, n, m, st, match, g):
                    res += 1
        return n * m - k - res


if __name__ == "__main__":
    sys.setrecursionlimit(6000)
    print(Solution().process())

おすすめ

転載: blog.csdn.net/qq_39445165/article/details/121457419