传送门 \looparrowright

题目描述

在一个 n×nn \times n 个方格的国际象棋棋盘上,马(骑士)可以攻击的棋盘方格如图所示。
QQ截图20200728233308.png
棋盘上某些方格设置了障碍,骑士不得进入。
对于给定的 n×nn \times n 个方格的国际象棋棋盘和障碍标志,计算棋盘上最多可以放置多少个骑士,使得它们彼此互不攻击。

输入格式

第一行有 22 个正整数 nnmm ,分别表示棋盘的大小和障碍数。接下来的 mm 行给出障碍的位置。每行 22 个正整数,表示障碍的方格坐标。

输出格式

将计算出的共存骑士数输出。

输入输出样例

输入 #1

3 2
1 1
3 3

输出 #1

5

说明/提示

对于全部的测试点,保证 1n2001 \leqslant n \leqslant 2000m<n20 \leqslant m \lt n^2

分析

如下图所示。我们将所有点分成 ×× 两种点,可以发现同种点所在位置无法互相攻击。将所在行设为横坐标,所在列设为纵坐标,打叉的点坐标之和为偶数,打勾的点坐标之和为奇数;显然,只有坐标和奇偶性不同的两个位置才可能互相攻击。不妨建立二分图,设坐标和为偶数的方格为左部,坐标和为奇数的方格为右部。
洛谷 P3355.png
二分图问题往往可以用网络流求解。建立超级源点 ss 和超级汇点 ttss 向各个左部点连边,边权为 11;各个右部点向 tt 连边,边权为 11ss 的出边和 tt 的入边都存在,代表一开始将所有没有障碍的位置放入骑士;若割去 ss 的一条入边,那么该边指向的节点所代表的方格就不会放置骑士。接着在左右两部节点之间建边,左部各个点向其互相冲突的至多八个右部点连边;这时,一定存在从 sstt 的路径,这条路径一定会包含一对互相冲突的节点,设为 a,ba,b,意味着存在 ssaa 的边和 bbtt 的边,即两个冲突的位置同时放了骑士;我们的任务是割去一些 ss 的出边和 tt 的入边,使得 sstt 互不相通,且割去的边的边权要尽量小(删掉最少的非法骑士),这就联想到了最小割。将左右两部之间的边的边权设为 ++\infty,该网络最小割即为要舍弃的骑士的数的总和,最初放置的 n×nmn\times n-m 个骑士减去舍弃的骑士数量即为答案。需要注意的是,有一些位置本身就设置了不能放骑士,建图时这些点不能有任何连边。
最后,最大流等于最小割,建完图跑一遍 Dinic\text{Dinic} 算法即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
/******************************************************************
Copyright: 11D_Beyonder All Rights Reserved
Author: 11D_Beyonder
Problem ID: 洛谷 P3355
Date: 7/28/2020
Description: Minimum Cut
*******************************************************************/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int MAX_POINT=40023;
const int MAX_EDGE=400024;
const int MAX_POS=206;
const int inf=0x3f3f3f3f;
//8个骑士可以攻击的位置
const int dx[]={-2,-2,-1,-1,1,1,2,2};
const int dy[]={-1,1,-2,2,-2,2,-1,1};
bool forbidden[MAX_POS][MAX_POS];
struct E
{
int to;
int cap;
int Next=-1;
}edge[MAX_EDGE];
int head[MAX_POINT],tot;
int s,t;
int depth[MAX_POINT];
int n,m;
void init();
inline void add_edge(int,int,int);
inline int ID(int,int);
bool bfs();
int dfs(int,int);
int Dinic();
int main()
{
int i,j,k;
cin>>n>>m;
int sum=n*n-m;//所有可行位置放上骑士
init();
for(i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
forbidden[x][y]=1;
}
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
if(forbidden[i][j]) continue;
if((i+j)&1) add_edge(ID(i,j),t,1);//右部
else//左部
{
add_edge(s,ID(i,j),1);
for(k=0;k<8;k++)
{
int x=i+dx[k];//行
int y=j+dy[k];//列
if(x<1||x>n||y<1||y>n) continue;
if(forbidden[x][y]) continue;
add_edge(ID(i,j),ID(x,y),inf);
}
}
}
}
cout<<sum-Dinic()<<endl;
return 0;
}
void init()
{
tot=1;
memset(head,-1,sizeof(head));
s=0;
t=n*n+1;
}
inline int ID(int x,int y) {return (x-1)*n+y;}
inline void add_edge(int u,int v,int cap)
{
tot++;
edge[tot].to=v;
edge[tot].cap=cap;
edge[tot].Next=head[u];
head[u]=tot;
//建立反边
tot++;
edge[tot].to=u;
edge[tot].cap=0;
edge[tot].Next=head[v];
head[v]=tot;
}
bool bfs()
{
memset(depth,0,sizeof(depth));
queue<int>q;
q.push(s);
depth[s]=1;
while(!q.empty())
{
int x=q.front();
q.pop();
for(register int i=head[x];~i;i=edge[i].Next)
{
int y=edge[i].to;
//残量网络上构建分层图
if(edge[i].cap&&!depth[y])
{
q.push(y);
depth[y]=depth[x]+1;
if(y==t) return 1;//汇点可达
}
}
}
return 0;
}
int dfs(int x,int flow)//当前节点 当前流量
{
//dfs 返回残量网络上可增广的流量
if(x==t) return flow;
int rest=flow;//rest 剩余流量
int temp;
for(register int i=head[x];~i&&rest;i=edge[i].Next)
{
int y=edge[i].to;
if(edge[i].cap&&depth[y]==depth[x]+1)
{
temp=dfs(y,min(rest,edge[i].cap));
if(!temp) depth[y]=0;//剪枝 去掉增广完毕的点
edge[i].cap-=temp;
edge[i^1].cap+=temp;
rest-=temp;
}
}
return flow-rest;
}
int Dinic()
{
int maxflow=0;
while(bfs()) maxflow+=dfs(s,inf);
return maxflow;
}