• 【洛谷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号点的最短路
    }
    
    败得义无反顾,弱得一无是处
  • 相关阅读:
    JUC原子类 1
    线程优先级和守护线程
    多线程中断
    关于html5不支持frameset的解决方法
    shell中$0,$?,$!等的特殊用法
    Linux GCC常用命令
    C/C++中extern关键字详解
    js实现iframe自适应高度
    java线程安全总结
    Linux平台Java调用so库-JNI使用例子
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu6545.html
Copyright © 2020-2023  润新知