[二分图]Knight

题目描述
一张大小为n*n的国际象棋棋盘,上面有一些格子被拿走了,棋盘规模n不超过200。马的攻击方向如下图,其中S处为马位置,标有X的点为该马的攻击点。
这里写图片描述
你的任务是确定在这个棋盘上放置尽可能多的马,并使他们不互相攻击。

Output
第一行有两个整数n,k
接下来k行每行两个整数x,y表示去掉的格子

分析
想当年我刚学DFS做跳马的时候想做这题←_←
这题是二分图例题里来的,那么肯定是二分图(滑稽)
好的不扯了,二分图中最大独立集的概念是顶点数-最小覆盖(即最大匹配)的到最大独立集中元素的个数
然后我们看一看,这题数据为200*200的矩阵,那么独立集的顶点数则为40000个。
什么?40000^2谁开得起这样大的数组?
那么我们必须考虑其他的方法。
但是我们可以明显地看出,最多最多的情况下,也只能放置20000个棋子(或更少)
于是我想到了几天前搜这题的时候讲到的一个奇偶建图的概念,即建图方式为(i+j)%2,若为1,做二分图下面的,若为0,做二分图上面的。
那么就很简单了。
记得顶点要减去被去掉的格子

#include <iostream>
#include <cstdio>
#include <memory.h>
using namespace std;
int n,m;
int f[20001],map[201][201],xl[20001][2],dx[8],dy[8];
bool r[20001],q[201][201];
int i,j,x,y,k,ans;
bool fin(int a)
{
    int i,d,w1,w2;
    for (i=0;i<8;i++)
    {
        w1=xl[a][0]+dx[i];w2=xl[a][1]+dy[i];
        if (w1<1||w1>n||w2<1||w2>n||q[w1][w2]||r[map[w1][w2]]) continue;
        d=f[map[w1][w2]];f[map[w1][w2]]=a;r[map[w1][w2]]=1;
        if (d==0||fin(d)) return true;
        f[map[w1][w2]]=d;
    }
    return false;
}
int main()
{
    scanf("%d%d",&n,&m);
    dx[0]=1;dx[1]=1;dx[2]=-1;dx[3]=-1;dx[4]=-2;dx[5]=-2;dx[6]=2;dx[7]=2;
    dy[0]=-2;dy[1]=2;dy[2]=-2;dy[3]=2;dy[4]=1;dy[5]=-1;dy[6]=1;dy[7]=-1;
    for (i=1;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        q[x][y]=1;
    }
    x=0;y=0;
    for (i=1;i<=n;i++)
    for (j=1;j<=n;j++)
    if (!q[i][j])
    {
        if ((i+j)%2)
        {
            x++;
            map[i][j]=x;
        }
        else
        {
            y++;
            map[i][j]=y;
            xl[y][0]=i;
            xl[y][1]=j;
        }
    }
    for (i=1;i<=y;i++)
    {
        memset(r,0,sizeof(r));
        if (fin(i)) ans++;
    }
    k=n*n-m-ans;
    printf("%d\n",k);
}
def main(): import sys input = sys.stdin.read().split() ptr = 0 n = int(input[ptr]) ptr += 1 m = int(input[ptr]) ptr += 1 obstacles = set() for _ in range(m): x = int(input[ptr]) - 1 # 转换为 0-based 索引 ptr += 1 y = int(input[ptr]) - 1 ptr += 1 obstacles.add((x, y)) # 1. 统计可放置的总格子数 & 构建二分图 color = [[(i + j) % 2 for j in range(n)] for i in range(n)] total = 0 graph = [[] for _ in range(n * n)] # 黑格 -> 白格的邻接表 # 骑士的 8 种移动方向 dirs = [(-2, -1), (-2, 1), (-1, -2), (-1, 2), (1, -2), (1, 2), (2, -1), (2, 1)] for i in range(n): for j in range(n): if (i, j) in obstacles: continue total += 1 if color[i][j] != 0: # 只处理黑格(0-based 染色) continue # 遍历所有可能的攻击位置 for dx, dy in dirs: ni, nj = i + dx, j + dy if 0 <= ni < n and 0 <= nj < n and (ni, nj) not in obstacles: # 转换为一维索引 u = i * n + j v = ni * n + nj graph[u].append(v) # 2. 迭代版匈牙利算法(非递归,避免栈溢出) def max_matching(): match_to = [None] * (n * n) # 记录白格对应的黑格匹配(列表实现) result = 0 for u in range(n * n): # 只处理黑格且未被障碍阻挡的位置 i, j = u // n, u % n if (i, j) in obstacles or color[i][j] != 0: continue visited = [False] * (n * n) stack = [(u, iter(graph[u]))] prev = {} # 记录路径,用于回溯更新匹配 found = False v_found = None # 记录找到的白格 while stack: node, neighbors = stack[-1] try: v = next(neighbors) if not visited[v]: visited[v] = True if match_to[v] is None: found = True v_found = v break else: # 记录路径,用于后续回溯 prev[v] = node stack.append((match_to[v], iter(graph[match_to[v]]))) except StopIteration: stack.pop() # 找到增广路径,更新匹配(列表索引访问) if found: v = v_found while v is not None: u_prev = prev.get(v, None) match_to[v] = u u = u_prev # 列表按索引访问,判断 u 合法性 v = match_to[u] if u is not None and 0 <= u < len(match_to) else None result += 1 return result # 3. 计算最大独立集 matching = max_matching() max_knights = total - matching print(max_knights) if __name__ == "__main__": main()
06-07
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值