[洛谷P3355]骑士共存问题 -二分图 - 最大独立集 - 匈牙利算法

[题目链接]https://www.luogu.com.cn/problem/P3355
【题目描述】
题目描述
在一个 n*n个方格的国际象棋棋盘上,马(骑士)可以攻击的棋盘方格如图所示。棋盘上某些方格设置了障碍,骑士不得进入
对于给定的 n*n 个方格的国际象棋棋盘和障碍标志,计算棋盘上最多可以放置多少个骑士,使得它们彼此互不攻击

输入格式
第一行有 2 个正整数n 和 m (1<=n<=200, 0<=m< n 2 n^2 n2),分别表示棋盘的大小和障碍数。接下来的 m 行给出障碍的位置。每行 2 个正整数,表示障碍的方格坐标。

输出格式
将计算出的共存骑士数输出

【分析】
正解是网络流,但是我硬是用匈牙利卡常过了

分析棋盘,连接棋盘的“日”字格对角线,表示两个端点不能共存。原题转化为去掉最少的点数使得任意两点之间都没有边相连,求一个最大独立集。
有定理二分图的最大独立集 = 点数-最大匹配数。
且可以看出骑士所影响的点和它所在的点颜色一定不同。对棋盘进行黑白染色,行号加列号为偶数的格子染成白色,行号加列号为奇数的格子染成黑色,黑、白色的格子分别作为二分图的左、右部结点。
用 (i^j)&1的方法快速染色,这也是二分图和网络流常用的技巧
如果两个格子是“日”的对角,能够相互攻击到,则在对应的结点之间连边(因为只有八个方向,所以不用额外建图,for循环判断)
匈牙利算法求二分图的最大独立集

卡常优化,267ms

#include <bits/stdc++.h>
using namespace std;
const int maxn = 205;
int n,m,ans;
bool mp[maxn][maxn],vis[maxn][maxn];
const int dx[8]={1,1,-1,-1,2,2,-2,-2};
const int dy[8]={-2,2,-2,2,-1,1,-1,1};
inline int get()
{
    char c;int f=0,d=1;
    while((c=getchar())<48||c>57)if(c=='-')d=-1;f=(f<<3)+(f<<1)+c-48;
    while((c=getchar())>=48&&c<=57)f=(f<<3)+(f<<1)+c-48;
    return d*f;
}
inline void read()
{   //int x,y;
   // scanf("%d%d",&n,&m);
    n = get();m = get();
    for(int i=1;i<=m;i++)
    {
        //scanf("%d%d",&x,&y);
        mp[get()][get()] = 1;
    }
}
int match[maxn][maxn][2];
inline int find(int x,int y)
{
    int xx,yy;
    for(int i=0;i<8;i++)
    {
        xx = x+dx[i],yy = y+dy[i];
        if(xx<1 || yy <1 || xx>n || yy>n)    continue;
        if(vis[xx][yy] || mp[xx][yy])       continue;
        vis[xx][yy] = 1;
        if(!match[xx][yy][0] || find(match[xx][yy][0],match[xx][yy][1]))
        {
            match[xx][yy][0] = x;
            match[xx][yy][1] = y;
            return 1;
        }
    }
    return 0;
}
inline void work()
{   ans = 0;
    for(register int i=1;i<=n;i++)
    {
        for(register int j=1;j<=n;j++)
        {
            if(!((i^j)&1) || mp[i][j]) continue;//快速染色
            memset(vis,0,sizeof(vis));
            ans+=find(i,j);
        }
    }
    printf("%d",n*n-m-ans);
}
int main()
{   //freopen("test.in","r",stdin);
    //freopen("test.out","w",stdout);
    read();
    work();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值