【GDOI2013】字母连接 题解

4 篇文章 0 订阅
2 篇文章 0 订阅

题面

有一个游戏,在平面上给定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,m16 ,暴力跑不过去。
发现 n ∗ m n*m nm 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);
   }	
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值