【洛谷6545】[CEOI2014] The Wall(最短路+网格图拆点)

点此看题面

  • 给定一张(n imes m)的网格图,其中有一些关键格(保证((1,1))是关键格),格子的每条边上都有一个边权。
  • 从网格图的左上角出发,沿着格边走出一条闭合回路,要求包围所有关键格,求最小的边权和(若一条边经过多次,则边权也计算多次)。
  • (n,mle400)

到每个关键格的最短路

我们求出从整张网格图的左上角到每个关键格左上角的最短路。

显然,最终答案走出的闭合回路一定包含这所有的最短路。(证明就考虑调整法,这里略去了)

而要包含一条最短路,等价于我们选择的闭合回路不能穿过这条最短路上任意一条边。

因此我们就是要找到一条边权和最小的闭合回路,包围了所有关键格且不穿过任一最短路上的边,但是这两个条件若想直接做无疑都非常棘手。

网格图拆点

考虑我们把一个格点拆成(4)个点用于处理在某个点上的方向转换,从左上开始,而顺时针顺序依次编号为(0sim 3)

如下图所示,红点是原本的点,蓝点是拆成的(4)个新点,黑线是格边:

然后考虑在这张图上处理我们的两个限制。

要求包围所有关键格,如图中淡蓝色方格所示,就是让它覆盖的这四个点不能走。

要求不穿过最短路上的边,如图中绿边所示,对于一条竖直边,就是让它上方端点的(2-3)边、下方端点的(0-1)边不能连;对于一条水平边,就是让它左侧端点的(1-2)边、右侧端点的(3-0)边不能连。

除了这两种特殊情况,如图中橙边所示,对于每个格点我们给它拆成的(4)个点间除对角线以外连上边权为(0)的边,对于每条格边我们给它两侧的两对点分别连上相应边权的边。

想要求出从左上角(0)号点出发的最小边权的闭合回路,其实只要把(0)号点删去,求出从(1)号点到(3)号点的最短路即可。

代码:(O(nmlognm))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 401
#define LL long long
#define PS (4*N*N)
#define ES (4*PS)
#define id(x,y) ((x)*(m+1)+(y))
#define ID(x,y,op) (id(x,y)*4+(op))
#define add(x,y,z) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].v=z)
using namespace std;
int n,m,a[N+5][N+5],b[N+5][N+5],c[N+5][N+5],ee,lnk[PS+5],D[PS+5];struct edge {int to,nxt,v;}e[ES+5];
I void Add(CI x,CI y,CI v) {add(x,y,v),add(y,x,v);}//连双向边
I void Try(CI x,CI y,CI v) {!D[x]&&!D[y]&&(Add(x,y,v),0);}//尝试连边,如果有删去的点就不连
typedef pair<LL,int> Pr;priority_queue<Pr,vector<Pr>,greater<Pr> > q;
LL dis[PS+5];int vis[PS+5],pre[PS+5];I void Dij(CI s,CI tot)//Dijkstra
{
	RI i,k;for(i=0;i<=tot;++i) dis[i]=1e18,vis[i]=0;q.push(make_pair(dis[s]=0,s));//初始化
	W(!q.empty()) if(k=q.top().second,q.pop(),!vis[k]) for(vis[k]=1,i=lnk[k];i;i=e[i].nxt)
		dis[e[i].to]>dis[k]+e[i].v&&(q.push(make_pair(dis[e[i].to]=dis[k]+e[i].v,e[i].to)),pre[e[i].to]=k);//pre记录前驱
}
int E[PS+5][4];I void Kill(CI x)//给一条最短路上所有边加限制
{
	if(x==id(0,0)) return;RI p=x/(m+1),q=x%(m+1),u=pre[x]/(m+1),v=pre[x]%(m+1);//记录当前点和前驱点坐标
	p==u+1&&(E[pre[x]][2]=E[x][0]=1),p==u-1&&(E[pre[x]][0]=E[x][2]=1),//竖直边
	q==v+1&&(E[pre[x]][1]=E[x][3]=1),q==v-1&&(E[pre[x]][3]=E[x][1]=1),Kill(pre[x]);//水平边;继续向前处理
}
int main()
{
	RI i,j,k;for(scanf("%d%d",&n,&m),i=1;i<=n;++i) for(j=1;j<=m;++j) scanf("%d",&a[i][j]);
	for(i=0;i^n;++i) for(j=0;j<=m;++j) scanf("%d",&b[i][j]),Add(id(i,j),id(i+1,j),b[i][j]);//初始竖直边
	for(i=0;i<=n;++i) for(j=0;j^m;++j) scanf("%d",&c[i][j]),Add(id(i,j),id(i,j+1),c[i][j]);//初始水平边
	for(Dij(id(0,0),(n+1)*(m+1)),i=1;i<=n;++i) for(j=1;j<=m;++j) a[i][j]&&//求出网格图左上角到所有关键格左上角的最短路
		(Kill(id(i-1,j-1)),D[ID(i-1,j-1,2)]=D[ID(i-1,j,3)]=D[ID(i,j-1,1)]=D[ID(i,j,0)]=1);//删去关键格覆盖的四点,给到关键格左上角最短路的所有边加限制
	for(D[ID(0,0,0)]=1,ee=i=0;i<=ID(n,m,3);++i) lnk[i]=0;//删去左上角的0号点;清空图
	for(i=0;i<=n;++i) for(j=0;j<=m;++j) for(k=0;k<=3;++k) !E[id(i,j)][k]&&(Try(ID(i,j,k),ID(i,j,(k+1)%4),0),0);//在每个点拆成的4个点间连边
	for(i=0;i^n;++i) for(j=0;j<=m;++j) Try(ID(i,j,3),ID(i+1,j,0),b[i][j]),Try(ID(i,j,2),ID(i+1,j,1),b[i][j]);//新的竖直边,分两侧
	for(i=0;i<=n;++i) for(j=0;j^m;++j) Try(ID(i,j,1),ID(i,j+1,0),c[i][j]),Try(ID(i,j,2),ID(i,j+1,3),c[i][j]);//新的水平边,分两侧
	return Dij(ID(0,0,1),(n+1)*(m+1)*4),printf("%lld
",dis[ID(0,0,3)]),0;//求左上角的1号点到3号点的最短路
}
败得义无反顾,弱得一无是处
原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu6545.html