题目概述
一张网格图,每个格子是海,浮冰或冰山,一只船(只占一个格子)只能放在海上。两只船放在同行同列需要满足他们之间至少有一座冰山。求最多能放多少船。
解题报告
一行一列只能放一个,一下就想到二分图最大匹配了,但是有障碍物,怎么办?其实问题的本质还是一样的,只不过我们需要这么处理:
由于出现了障碍物,“一行”和“一列”已经不能是原来意义上的一行和一列了。在同一行,如果被障碍物隔开,其实应该视作“多行”。也就是说我们可以把每个格子标两个号:“行”号和“列”号,障碍和浮冰处没有标号。在一行(一列)中,如果被障碍物隔开,就需要将之后的格子的标号+1,但被浮冰隔开则没有影响。这么处理之后,直接刷二分图最大匹配就行了。
示例程序
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=50,maxm=50,maxt=maxn*maxm;
int te,n,m,sumx,sumy,ans;
char pic[maxn+5][maxm+5];
int idx[maxn+5][maxm+5],idy[maxn+5][maxm+5];
int E,lnk[maxt/2+5],who[maxt/2+5],son[maxt+5],nxt[maxt+5];
bool vis[maxt/2+5];
char getrch() {char ch=getchar();while (ch!='*'&&ch!='o'&&ch!='#') ch=getchar();return ch;}
void make_x() //为行标号
{
sumx=0;memset(idx,0,sizeof(idx));
for (int i=1,j=1;i<=n;i++,j=1)
while (j<=m)
{
for (;j<=m&&pic[i][j]=='#';j++);sumx+=j<=m;
for (;j<=m&&pic[i][j]!='#';j++)
if (pic[i][j]=='*') idx[i][j]=sumx;
}
}
void make_y() //为列标号
{
sumy=0;memset(idy,0,sizeof(idy));
for (int j=1,i=1;j<=m;j++,i=1)
while (i<=n)
{
for (;i<=n&&pic[i][j]=='#';i++);sumy+=i<=n;
for (;i<=n&&pic[i][j]!='#';i++)
if (pic[i][j]=='*') idy[i][j]=sumy;
}
}
void Add(int x,int y) {son[++E]=y;nxt[E]=lnk[x];lnk[x]=E;}
bool Find(int x)
{
if (vis[x]) return false;vis[x]=true;
for (int j=lnk[x];j;j=nxt[j])
if (!who[son[j]]||Find(who[son[j]]))
{
who[son[j]]=x;
return true;
}
return false;
}
int main()
{
freopen("program.in","r",stdin);
freopen("program.out","w",stdout);
scanf("%d",&te);
while (te--)
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
pic[i][j]=getrch();
make_x();make_y();
E=0;memset(lnk,0,sizeof(lnk));
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
if (idx[i][j]&&idy[i][j]) //i,j放船意味着idx[i][j]->idy[i][j]
Add(idx[i][j],idy[i][j]);
ans=0;memset(who,0,sizeof(who));
for (int i=1;i<=sumx;i++) //最大匹配
memset(vis,0,sizeof(vis)),ans+=Find(i);
printf("%d\n",ans);
}
return 0;
}