前言
话说这题和SSL-1144一模一样,连数据范围和样例都一模一样,不同的是洛谷的只有网络流能过,匈牙利会超时。。。
于是我便在洛谷上水了篇博客,不知道过没过。。。
链接
https://www.luogu.org/problemnew/show/P3355
思路
其实这道题匈牙利也能过91分。。。但时间却是网络流的4倍。。。
匈牙利算法
奇偶建图(如果不这样会TLE更多的点),所以我们把奇数的点放在左边,偶数的点放在右边,若能攻击到彼此,则将它们连边,再求一边匈牙利
最大流求法
在匈牙利算法建模的基础上在左右两侧分别加上源点和汇点,并将源点和所有的奇数点连边,汇点和所有的偶数点连边(容量都为1)
若能攻击到彼此,则将它们连边(容量为无穷大),再跑一遍最大流即可
匈牙利算法代码
#include<cstdio>
#include<cstring>
#define N 202
#define M 20002
#define r(i,a,b) for(int i=a;i<=b;i++)
using namespace std;int n,m,ans,one,g[N][N],two,link[M],xq[M][2];
bool vis[M],ok[N][N];
const short dx[8]={1,1,-1,-1,2,2,-2,-2};
const short dy[8]={-2,2,-2,2,-1,1,-1,1};
int read()//输入流
{
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;
}
bool check(int x,int y)//判断是否满足要求
{
if(x<1||x>n||y<1||y>n) return false;
if(ok[x][y]||vis[g[x][y]]) return false;
return true;
}
bool find(int x)
{
int k=0,q=0,x1,y1;
r(i,0,7)//这里没有必要建邻接表,因为骑士顶多跳周围的八个点
{
x1=xq[x][0]+dx[i];
y1=xq[x][1]+dy[i];//取下坐标
if(check(x1,y1))//没有跳出去则进行
{
k=g[x1][y1];
q=link[k];
link[k]=x;
vis[k]=true;
if(!q||find(q)) return true;
link[k]=q;
}
}
return false;
}
int main()
{
n=read();m=read();
r(i,1,m) ok[read()][read()]=true;
r(i,1,n)
r(j,1,n)
if(!ok[i][j])
{
if((i+j)&1)
g[i][j]=++one;
else
{
g[i][j]=++two;
xq[two][0]=i;
xq[two][1]=j;//奇偶建图
}
}
ans=n*n-m;
r(i,1,two)
{
memset(vis,0,sizeof(vis));
if(find(i)) ans--;
}
printf("%d",ans);//输出
}
最大流代码
#include<cstring>
#include<cstdio>
#include<queue>
#define M 500001//这个一定要开得足够大
#define k(i,j) (i-1)*n+j//用这个表示每个点的标号
#define min(a,b) a<b?a:b
#define max(a,b) a>b?a:b
using namespace std;int id,f,n,m,d[M],l[M],s,t,sum,a;char c;
bool ok[201][201];
int read()
{
char c;f=0;
while(c=getchar(),c<=47||c>=58);f=(f<<3)+(f<<1)+c-48;
while(c=getchar(),c>=48&&c<=57) f=(f<<3)+(f<<1)+c-48;
return f;
}
struct node{int next,to,w;}e[M];int tot;
const short dx[8]={1,1,-1,-1,2,2,-2,-2};
const short dy[8]={2,-2,2,-2,1,-1,1,-1};//八个方向
void add(int u,int v,int w)//建边
{
e[tot].to=v;e[tot].w=w;e[tot].next=l[u];l[u]=tot++;
e[tot].to=u;e[tot].w=0;e[tot].next=l[v];l[v]=tot++;
return;
}
bool bfs()//分层图建设
{
memset(d,-1,sizeof(d));
queue<int>q;
q.push(s);d[s]=0;
while(q.size())
{
int x=q.front();q.pop();
for(int i=l[x];i!=-1;i=e[i].next)
{
int y=e[i].to;
if(e[i].w&&d[y]==-1)
{
d[y]=d[x]+1;
q.push(y);
if(y==t) return true;
}
}
}
return false;
}
int dfs(int x,int flow)//寻找可行流
{
if(x==t||!flow) return flow;
int rest=0,f;
for(int i=l[x];i!=-1;i=e[i].next)
{
int y=e[i].to;
if(d[x]+1==d[y]&&e[i].w)
{
rest+=(f=dfs(y,min(flow-rest,e[i].w)));
e[i].w-=f;e[i^1].w+=f;
}
}
if(!rest) d[x]=-1;
return rest;
}
int dinic()
{
int r=0;
while(bfs()) r+=dfs(s,1e9);//求最大流
return r;//返回
}
int main()
{
memset(l,-1,sizeof(l));
n=read();m=read();s=0;t=n*n+1;sum=n*n-m;
while(m--) ok[read()][read()]=true;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
if(ok[i][j]) continue;//若有障碍则跳过
if((i+j)&1)
{
add(s,k(i,j),1);//与源点相连
for(int o=0;o<8;o++)
{
int nx=i+dx[o],ny=j+dy[o];
if(nx<1||ny<1||nx>n||ny>n||ok[nx][ny]) continue;
id=k(nx,ny);
add(k(i,j),id,1e9);//能攻击到则连边,与匈牙利算法不同的是这里是奇数建边,其实无论奇数建还是偶数建都不会有什么影响
}
} else add(k(i,j),t,1);//连边
}
printf("%d",sum-dinic());//输出
}
后记
记得在学最大流的时候,老师跟我们说让我们用网络流去做最大匹配的题,当时我还有点不解,做了这道题后算是有了一个比较深入的了解吧。
网络流24题的其他题貌似也有许多可以用匈牙利算法做,不明白为什么会被评为省选/NOI-。。。