题面
有一个游戏,在平面上给定m行n列的格子。在部分格子上存在障碍物,在没有障碍物的格子上可能有英文字母。现在要求在空格子上建立路径,使得每条路径两端分别有一个字母,且两端的字母互不相同,并且所有字母都通过路径与另一字母连接。每个空格子最多被用于一段路径,每段路径为下列六钟形式之一。
例如在下图中,阴影格子表示存在障碍物的格子,白色格子表示空格子,初始时给出的图为图(a)。图(b)是一个合法的解,其中包含两条路径,分别为路径AC和路径AB。图(c)是一个不合法的解,因为其中一条路径的两端为相同的两个字母A。图(d)也是一个不合法的解,因为其中一个格子被用于两条路径中。
现在给定一个初始的图,问最少需要在多少个格子上建立路径才可以完成以上任务。
数据范围
对于30%的数据,平面上最多只有 4 4 4 个字母,并且各不相同。
对于50%的数据,平面上的字母各不相同。
对于100%的数据,T不超过 3 3 3, m m m 和 n n n 不超过 16 16 16,字母个数为正偶数且不超过 8 8 8,最多只有两个字母相同。
题解
非常好的一道网络流题目。
发现
n
,
m
≤
16
n,m \le 16
n,m≤16 ,暴力跑不过去。
发现
n
∗
m
n*m
n∗m 才
256
256
256 ,可以将矩阵转化成图,考虑网络流解决。
首先点的起点和终点可以爆搜枚举(字母总数不大于
8
8
8)。
因为每一个点只会经过一次,所以考虑将一个点拆成两个点,一个点负责流入,一个点负责流出,然后两个点之间连一条流量为
1
1
1 代价为
0
0
0 的边,这样可以保证每一个点至多可以被经过一次。将起点和终点分完之后,可以将源点流向
s
s
s 终点流向
t
t
t 跑一遍费用流就可以了(注意每枚举完一次就要重新建一遍图,因为图的信息已经改变了)。
对于两个编号相同的点,强行控制他们都为起点或都为终点即可。
思想为将矩阵转化为图,以后有数据较小的矩阵时可以尝试一下转化为图,利用网络流解决问题。
AC代码
//White_gugu's code
#include<cstring>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
struct node{
int wz,id;
}let[20];
int head[200200],to[200200],nxt[200200],val[200200],flo[200200],sa1,sa2;
int T,n,m,cnt,nod,pop,too[22][22],fx[8]={0,0,1,-1},fy[8]={1,-1,0,0},s,t,ans1,ans2,ans=21000000;
char a[22][22];
bool bz[22],vis[20020];
int lst[200200],dep[200200],sp[200200],dl[2000100],flow[200200],num[200200];
void xx(int u,int v,int w,int s)
{
to[cnt]=v;
val[cnt]=w;
flo[cnt]=s;
nxt[cnt]=head[u];
head[u]=cnt;
cnt++;
}
void lian()
{
memset(head,-1,sizeof(head)),cnt=0;
for(int x=1;x<=n;x++)
for(int y=1;y<=m;y++)
{
xx(too[x][y],too[x][y]+n*m,0,1),xx(too[x][y]+n*m,too[x][y],0,0);
if(a[x][y]=='#')
continue;
for(int k=0;k<4;k++)
{
int tx=fx[k]+x,ty=fy[k]+y;
if(tx>=1&&tx<=n&&ty>=1&&ty<=m&&a[tx][ty]!='#')
xx(too[x][y]+n*m,too[tx][ty],(a[tx][ty]=='.'),1),xx(too[tx][ty],too[x][y]+n*m,-(a[tx][ty]=='.'),0);
}
}
for(int i=1;i<=pop;i++)
{
if(bz[i]==0)
xx(s,let[i].wz,0,1),xx(let[i].wz,s,0,0);
else
xx(let[i].wz+n*m,t,0,1),xx(t,let[i].wz+n*m,0,0);
}
return;
}
bool spfa()
{
memset(sp,127/3,sizeof(sp));
memset(flow,127/3,sizeof(flow));
lst[t]=-1;
int h=0,ti=1;
dl[ti]=s;
vis[s]=1;
sp[s]=0;
while(h<ti)
{
h++;
int x=dl[h];
// printf("%d %d\n",x,sp[x]);
vis[x]=0;
for(int i=head[x];i!=-1;i=nxt[i])
{
int y=to[i];
if(flo[i]>0&&sp[y]>sp[x]+val[i])
{
sp[y]=sp[x]+val[i];
lst[y]=x;
num[y]=i;
flow[y]=min(flow[x],flo[i]);
if(!vis[y])
ti++,dl[ti]=y,vis[y]=1;
}
}
}
return lst[t]!=-1;
}
void wll()
{
ans1=0,ans2=0;
while(spfa())
{
ans1+=flow[t];
ans2+=sp[t];
int now=t;
while(now!=s)
{
flo[num[now]]-=flow[t];
flo[num[now]^1]+=flow[t];
now=lst[now];
}
}
}
void dfs(int x,int y)
{
if(y>pop/2)
return;
if(x==n+1)
{
if(y!=pop/2)
return;
if(bz[sa1]!=bz[sa2])
return;
lian();
wll();
if(ans1==pop/2)
ans=min(ans,ans2);
head[s]=-1,head[t]=-1;
return;
}
bz[x]=1;
dfs(x+1,y+1);
bz[x]=0;
dfs(x+1,y);
}
int main()
{
scanf("%d",&T);
while(T--)
{
ans=21000000;
pop=0,nod=0,sa1=0,sa2=0;
scanf("%d %d",&n,&m);
s=0,t=2*n*m+1;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
cin>>a[i][j];
nod++,too[i][j]=nod;
if(a[i][j]>='A'&&a[i][j]<='Z')
pop++,let[pop].wz=nod,let[pop].id=a[i][j]-'A';
}
for(int i=1;i<=pop;i++)
for(int j=1;j<=pop;j++)
if(let[i].id==let[j].id&&i!=j)
{
sa1=i,sa2=j;
break;
}
dfs(1,0);
if(ans==21000000)
printf("-1\n");
else
printf("%d\n",ans);
}
}