您的位置:首页 > 理论基础 > 计算机网络

codevs 1922 骑士共存问题||二分图||最大独立集||二分图匹配||Dinic与匈牙利算法的讨论||网络流

2015-11-26 15:57 656 查看
**

1922 骑士共存问题

**

**

题目描述 Description

**

在一个n*n个方格的国际象棋棋盘上,马(骑士)可以攻击的棋盘方格如图所示。棋盘

上某些方格设置了障碍,骑士不得进入。

对于给定的n*n个方格的国际象棋棋盘和障碍标志,计算棋盘上最多可以放置多少个骑

士,使得它们彼此互不攻击。

**

输入描述 Input Description

**

第一行有2 个正整数n 和m (1<=n<=200, 0<=m < n^2),

分别表示棋盘的大小和障碍数。接下来的m 行给出障碍的位置。每行2 个正整数,表示障碍的方格坐标。

**

输出描述 Output Description

**

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

**

样例输入 Sample Input

**

3 2

1 1

3 3

**

样例输出 Sample Output

**

5

**

数据范围及提示 Data Size & Hint

**

详见试题

**

Solution

**

对二分图的简单证明:

我们可以先画一张图 任意选一个格点记为 1 与他能攻击的格点记为2 然后不断拓展下去 我们最后会发现:1和2不会重复出现==>是两个不相交的集合

类似酱~~

1 2 1 2 1 2

2 1 2 1 2 1

1 2 1 2 1 2

2 1 2 1 2 1

1 2 1 2 1 2

2 1 2 1 2 1

原因我们可以简单证明 设横向绝对值为2纵向绝对值为1 的走法走了 x次  纵向为1 横向为2 的走法走了y次。只有x 与 y 同奇(同偶)才可以使得起点到重点的delta x 与 delta y 同奇(同偶)


反亦反之.

以下摘自某神题解。

Solution_ID:296

【问题分析】

二分图最大独立集,转化为二分图最大匹配,从而用最大流解决。

【建模方法】

首先把棋盘黑白染色,使相邻格子颜色不同。把所有可用的黑色格子看做二分图X集合中顶点,可用的白色格子看做Y集合顶点。建立附加源S汇T,从S向X集合中每个顶点连接一条容量为1的有向边,从Y集合中每个顶点向T连接一条容量为1的有向边。从每个可用的黑色格子向骑士一步能攻击到的可用的白色格子连接一条容量为无穷大的有向边。求出网络最大流,要求的结果就是可用格子的数量减去最大流量。

【建模分析】

用网络流的方法解决棋盘上的问题,一般都要对棋盘黑白染色,使之成为一个二分图。放尽可能多的不能互相攻击的骑士,就是一个二分图最大独立集问题。有关二分图最大独立集问题,更多讨论见《最小割模型在信息学竞赛中的应用》作者胡伯涛。

该题规模比较大,需要用效率较高的网络最大流算法解决。

二分图最大独立集求法证明:

二分图的最大独立集

如果一个图是二分图,那么它的最大独立集就是多项式时间可以解决的问题了 |最大独立集| = |V|-|最大匹配数|

证明:

设最大独立集数为U,最大匹配数为M,M覆盖的顶点集合为EM。

为了证明|U|=|V|-|M|,我们分两步证明|U|<=|V|-|M|和|U|>=|V|-|M|

1 先证明 |U|<=|V|-|M|

M中的两个端点是连接的,所有M中必有一个点不在|U|集合中,所以|M|<=|V|-|U|

2 再证明|U|>=|V|-|M|

假设(x,y)属于M

首先我们知道一定有|U|>=|V|-|EM|,那么我们将M集合中的一个端点放入U中可以吗?

假设存在(a,x),(b,y),(a,b)不在EM集合中

如果(a,b)连接,则有一个更大的匹配存在,矛盾

如果(a,b)不连接,a->x->y->b有一个新的增广路,因此有一个更大的匹配,矛盾

所以我们可以了解到取M中的一个端点放入U中肯定不会和U中的任何一个点相连,所以|U|>=|V|-|EM|+|M|=|V|-|M|

所以,|U|=|V|-|M|

让我们来试一下Dinic匈牙利的区别

先贴一份TLE掉2个点的代码…有一些地方可以优化…明天再试…

**

Code

**

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

const int maxn=210;

struct data{int to,next;}e[maxn*maxn*4];
int mat[maxn*maxn],n,m,mark[maxn][maxn],tot,head[maxn*maxn],cnt,ans;
void ins(int u,int v){cnt++;e[cnt].to=v;e[cnt].next=head[u];head[u]=cnt;}
int xx[8]={2,2,-2,-2,1,1,-1,-1},yy[8]={1,-1,1,-1,2,-2,2,-2};
bool used[maxn*maxn];
bool dfs(int x)
{
for(int i=head[x];i;i=e[i].next)
if(!used[e[i].to])
{
used[e[i].to]=1;
if(!mat[e[i].to]||dfs(mat[e[i].to]))
{
mat[e[i].to]=x;
return true;
}
}
return false;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)mark[i][j]=++tot;
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
mark[x][y]=0;
}
for(int i=1;i<=n;i++)
for(int j=(i%2==0?2:1);j<=n;j+=2)
{
if(mark[i][j])
for(int k=0;k<8;k++)
{
int nx=i+xx[k],ny=j+yy[k];
if(nx<1||nx>n||ny<1||ny>n||!mark[nx][ny])continue;
ins(mark[i][j],mark[nx][ny]);
}
}
for(int i=1;i<=n;i++)
for(int j=(i%2==0?2:1);j<=n;j+=2)
if(mark[i][j])
{
memset(used,0,sizeof(used));
if(dfs(mark[i][j]))
ans++;
}
printf("%d",tot-ans-m);
return 0;
}


测试点#kni0.in 结果:AC 内存使用量: 256kB 时间使用量: 1ms
测试点#kni1.in 结果:AC 内存使用量: 256kB 时间使用量: 1ms
测试点#kni10.in 结果:TLE 内存使用量: 1516kB 时间使用量: 2000ms
测试点#kni2.in 结果:AC 内存使用量: 256kB 时间使用量: 1ms
测试点#kni3.in 结果:AC 内存使用量: 256kB 时间使用量: 1ms
测试点#kni4.in 结果:AC 内存使用量: 256kB 时间使用量: 1ms
测试点#kni5.in 结果:AC 内存使用量: 364kB 时间使用量: 1ms
测试点#kni6.in 结果:AC 内存使用量: 360kB 时间使用量: 1ms
测试点#kni7.in 结果:AC 内存使用量: 364kB 时间使用量: 4ms
测试点#kni8.in 结果:AC 内存使用量: 1128kB 时间使用量: 193ms
测试点#kni9.in 结果:TLE 内存使用量: 2156kB 时间使用量: 2000ms


Updata… 并没有什么卵用…

去试一下Dinic…

我赵日天不服….我叶良辰不服…为什么第一个题解就可以过…他分明用vector存的…为什么比我大链式前向星快!

Update

想爆粗口奈何…

A掉惹QAQ…

起初按着题解的存边方式存的…然后舒缓心情(我不会告诉你们是在玩 Flappy bird的TAT)发现…链式前向星的存法是反着的!!!

然后就没有然后了QAQ…

现在再去试一下Dinic…







Dinic有种弃疗的感觉QAQ…母鸡为何WA了…

顿时想呵呵自己一脸…插反边的时候容量给插成w了….TAT….

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;

const int maxn=201,S=maxn*maxn-2,T=maxn*maxn-1,inf=(1<<30);
struct data{int to,w,next;}e[maxn*maxn*10];
int head[maxn*maxn],cnt=1,mark[maxn][maxn],n,m,h[maxn*maxn],tot;
void ins(int u,int v,int w){cnt++;e[cnt].to=v;e[cnt].next=head[u];head[u]=cnt;e[cnt].w=w;}
void insert(int u,int v,int w){ins(u,v,w);ins(v,u,0);}
int xx[8]={-1, -2, -1, -2, 2, 2, 1, 1},yy[8]={2, 1, -2, -1, -1, 1, -2, 2};
bool bfs(){
memset(h,-1,sizeof(h));
h[S]=0;
queue<int> q;
q.push(S);
while(!q.empty())
{
int x=q.front();q.pop();
for(int i=head[x];i;i=e[i].next)
if(e[i].w&&h[e[i].to]<0)
{
q.push(e[i].to);
h[e[i].to]=h[x]+1;
}
}
if(h[T]==-1)return 0;
return 1;
}
int dfs(int x,int f){
if(x==T)return f;
int w,used=0;
for(int i=head[x];i;i=e[i].next)
if(e[i].w&&h[e[i].to]==h[x]+1)
{
w=f-used;
w=dfs(e[i].to,min(w,e[i].w));
e[i].w-=w;
e[i^1].w+=w;
used+=w;
if(used==f)return f;
}
if(!used)h[x]=-1;
return used;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
mark[i][j]=++tot;
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
mark[x][y]=0;
}
for(int i=1;i<=n;i++)
for(int j=(i%2==0?2:1);j<=n;j+=2)
{
insert(S,mark[i][j],1);
if(mark[i][j])
for(int k=0;k<8;k++)
{
int nx=i+xx[k],ny=j+yy[k];
if(nx<1||nx>n||ny<1||ny>n||!mark[nx][ny])continue;
insert(mark[i][j],mark[nx][ny],1);
}
}
for(int i=1;i<=n;i++)
for(int j=((i%2==0)?1:2);j<=n;j+=2)
if(mark[i][j])
insert(mark[i][j],T,1);
int ans=0;
while(bfs())ans+=dfs(S,inf);
printf("%d\n",n*n-m-ans);
return 0;
}


测试点#kni0.in  结果:AC    内存使用量:  360kB     时间使用量:  0ms
测试点#kni1.in  结果:AC    内存使用量:  364kB     时间使用量:  1ms
测试点#kni10.in  结果:AC    内存使用量:  3692kB     时间使用量:  291ms
测试点#kni2.in  结果:AC    内存使用量:  364kB     时间使用量:  1ms
测试点#kni3.in  结果:AC    内存使用量:  364kB     时间使用量:  1ms
测试点#kni4.in  结果:AC    内存使用量:  360kB     时间使用量:  0ms
测试点#kni5.in  结果:AC    内存使用量:  488kB     时间使用量:  1ms
测试点#kni6.in  结果:AC    内存使用量:  492kB     时间使用量:  0ms
测试点#kni7.in  结果:AC    内存使用量:  620kB     时间使用量:  4ms
测试点#kni8.in  结果:AC    内存使用量:  2152kB     时间使用量:  88ms
测试点#kni9.in  结果:AC    内存使用量:  5484kB     时间使用量:  163ms


Dinic的确要比匈牙利算法快得多…

哔哔哔= =【这里之前错掉了# #】

更正一下 匈牙利的复杂度是O(nm)【实在抱歉- -】[这里感谢 @凯乐报 指正]

而且记得貌似当边的容量为1的时候dinic的时间复杂度会下降,但是找不到了…QAQ

Update: Dinic在这里的复杂度应该是O(m*sqrt(n))

对了-补一下Hungry的代码,对比一下

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

const int maxn=210;

struct data{int to,next;}e[maxn*maxn*4];
int mat[maxn*maxn],n,m,mark[maxn][maxn],tot,head[maxn*maxn],cnt,ans,map[maxn*maxn];
void ins(int u,int v){cnt++;e[cnt].to=v;e[cnt].next=head[u];head[u]=cnt;}
int xx[8]={-1, -2, -1, -2, 2, 2, 1, 1},yy[8]={2, 1, -2, -1, -1, 1, -2, 2};
bool used[maxn*maxn];
bool dfs(int x)
{
for(int i=head[x];i;i=e[i].next)
if(!used[e[i].to])
{
used[e[i].to]=1;
if(!mat[e[i].to]||dfs(mat[e[i].to]))
{
mat[e[i].to]=x;
return true;
}
}
return false;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)mark[i][j]=++tot;
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
mark[x][y]=0;
}
for(int i=1;i<=n;i++)
for(int j=(i%2==0?2:1);j<=n;j+=2)
{
if(mark[i][j])
for(int k=0;k<8;k++)
{
int nx=i+xx[k],ny=j+yy[k];
if(nx<1||nx>n||ny<1||ny>n||!mark[nx][ny])continue;
ins(mark[i][j],mark[nx][ny]);
}
}
cnt=tot;
tot=0;
for(int i=1;i<=n;i++)
for(int j=(i%2==0?2:1);j<=n;j+=2)
if(mark[i][j])
{
memset(used,0,n*n);
if(dfs(mark[i][j]))
ans++;
}
printf("%d",cnt-ans-m);
return 0;
}


运行结果
测试点#kni0.in 结果:AC 内存使用量: 256kB 时间使用量: 1ms
测试点#kni1.in 结果:AC 内存使用量: 256kB 时间使用量: 1ms
测试点#kni10.in 结果:AC 内存使用量: 1516kB 时间使用量: 683ms
测试点#kni2.in 结果:AC 内存使用量: 256kB 时间使用量: 1ms
测试点#kni3.in 结果:AC 内存使用量: 256kB 时间使用量: 0ms
测试点#kni4.in 结果:AC 内存使用量: 256kB 时间使用量: 0ms
测试点#kni5.in 结果:AC 内存使用量: 256kB 时间使用量: 1ms
测试点#kni6.in 结果:AC 内存使用量: 256kB 时间使用量: 1ms
测试点#kni7.in 结果:AC 内存使用量: 364kB 时间使用量: 0ms
测试点#kni8.in 结果:AC 内存使用量: 1004kB 时间使用量: 68ms
测试点#kni9.in 结果:AC 内存使用量: 2540kB 时间使用量: 100ms
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: