NKOJ-1569 骑士共存 <最大独立集><染色法建图>

17 篇文章 0 订阅

P1569 【线性规划与网络流24题#24】骑士共存
时间限制 : 10000 MS 空间限制 : 65536 KB

问题描述
0 X 0 X 0
X 0 0 0 X
0 0 S 0 0
X 0 0 0 X
0 X 0 X 0

在一个n*n个方格的国际象棋棋盘上,马(骑士)可以攻击的棋盘方格如图所示。棋盘上某些方格设置了障碍,骑士不得进入。(S代表骑士的位置,X代表他可以攻击到的点)

对于给定的n*n个方格的国际象棋棋盘和障碍标志,计算棋盘上最多可以放置多少个骑士,使得它们彼此互不攻击。

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

输出格式
一个整数,计算出的共存骑士数

样例输入
3 2
1 1
3 3
样例输出
5

来源 二分图

这是个假的国际象棋

题解

这道题目建图是真的神奇
因为它并不是为了求最大匹配而建图
而是为了求最大独立集…

引入概念

所谓最大独立集,就是求最多个相互之间没有边连接的点

比如 1-2 2-3
假如我们删去2这个点
那么剩下的 1 3之间是没有边连接的
同时这样也是我们所能求到的最多的独立的点数
那么1 3就是这个图的最大独立集

关于最大独立集,有一个结论(目前我还不想证(主要是证不到,说起也很难意会))
最大独立集内点的个数=所有的点数-最大匹配边数

建图

我们比较容易(假的)想到的就是一个骑士和他所能攻击到的点分置两边
这个样子每一条边对应的是一个攻击关系

而我们要做的就是求这个图的最大独立集
也就是说,尽量少地删除点,达到把所有边都删除的目的

为什么要求这些呢??

因为当我们把所有的边都删除之后,剩下的点之间就不构成了攻击关系
那么这些点就是可以共存的

建图注意

这道题目是很容易重复存边的
为什么呢
比如下图

10
00
02

我们如果依次读点进行判断
那么我们会加入两条边
1-2
2-1
也就是说,分边之后会有

1 2
| |
2 1

这个图的最大独立集是2(删除下面两个点)
这个答案显然是错的

因为1-2和2-1表达的是同一个攻击关系,我们实际上只需要且仅能存一条边
那么我们就需要一个判断方式,避免重复存边

于是,我们就想到了染色法建图

无重复的建图方式

我们对一个图做标记

0 1 0 1 0           0 X 0 X 0
1 0 1 0 1    对应    X 0 0 0 X 
0 1 0 1 0  ——————>  0 0 S 0 0
1 0 1 0 1           X 0 0 0 X
0 1 0 1 0           0 X 0 X 0

不难发现,骑士所在的点的颜色是0,而他所能攻击到的点所在的颜色都是1
所以我们在存边的时候,只存骑士所在的点能够攻击到的点连成的边

进一步扩展:
所有能够攻击到某个颜色为0的点的颜色都是1

所以我们只需要读取颜色为1的点(或者颜色为0的点)存边,即可避免重复

而且我们不会漏存

假设存在某个颜色为0的点能够攻击到1
那么1也一定能够攻击到这个0

绝对不存在0能够攻击到1而1攻击不到0的情况

于是我们就能够的到结果了

我们用染色法建图(详见1521),然后删去障碍点
对某一种颜色的点进行存边,求出最大匹配数
根据结论,求出最大独立集即可

附上代码

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

int n,m,res=0,fath[45678];
int maps[234][234],all=0,star[45678],ent[45678],nxt[45678];
bool went[45678];

void add(int s,int e)
{
    nxt[++all]=star[s];
    star[s]=all;
    ent[all]=e;
}

bool find(int s)
{
    int e,bian;
    for(bian=star[s],e=ent[bian];bian;bian=nxt[bian],e=ent[bian])
    if(!went[e])
    {
        went[e]=1;
        if(!fath[e]||find(fath[e]))
        {
            fath[e]=s;
            return 1;
        }
    }
    return 0;
}

int main()
{
    int A=1,B=1,a,b;
    scanf("%d%d",&n,&m);
    for(int a=1;a<=n;a++)
    for(int b=1;b<=n;b++)maps[a][b]=(a+b%2==0)?A++:B++;//科学染色 
    while(scanf("%d%d",&a,&b)!=EOF)maps[a][b]=0;//删点 
    for(int a=1;a<=n;a++)
    for(int b=1+a%2;b<=n;b=b+2)//交替读取,只读取相同颜色的点 
    if(maps[a][b])
    {
        if(a-1>0)
        {
            if(b-2>0&&maps[a-1][b-2])add(maps[a][b],maps[a-1][b-2]);
            if(b+2<=n&&maps[a-1][b+2])add(maps[a][b],maps[a-1][b+2]);
        }
        if(a-2>0)
        {
            if(b-1>0&&maps[a-2][b-1])add(maps[a][b],maps[a-2][b-1]);
            if(b+1<=n&&maps[a-2][b+1])add(maps[a][b],maps[a-2][b+1]);
        }
        if(a+1<=n)
        {
            if(b-2>0&&maps[a+1][b-2])add(maps[a][b],maps[a+1][b-2]);
            if(b+2<=n&&maps[a+1][b+2])add(maps[a][b],maps[a+1][b+2]);
        }
        if(a+2<=n)
        {
            if(b-1>0&&maps[a+2][b-1])add(maps[a][b],maps[a+2][b-1]);
            if(b+1<=n&&maps[a+2][b+1])add(maps[a][b],maps[a+2][b+1]);
        }
    }//科学存边 
    for(int a=1;a<=n;a++)
    for(int b=1+a%2;b<=n;b=b+2)//相同颜色的点在一边,所以我们只读取相同颜色的点 
    if(maps[a][b])
    {
        memset(went,0,sizeof(went));
        if(find(maps[a][b]))res++;
    } 
    printf("%d",n*n-m-res);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值