[最短路]luogu P6545 [CEOI2014]The Wall

题面

https://www.luogu.com.cn/problem/P6545

分析

一个定理:城墙必然包含左上角到各村庄的左上角的最短路

如何证明:考虑当前有一个城墙方案,不包含某条最短路,不难证明此时城墙必然将最短路分割为数段

将在被围住区域外的最短路补入城墙,原城墙部分删除,肯定更优,因为是最短路,而且扩增了城墙范围

多次这样补足之后,不难发现最优方案的城墙必然包含所有最短路

那么SPFA跑完最短路,然后此时城墙方案就变成了:不穿过最短路树的连边情况下跑出来的最短路

关于不穿过最短路,可以将一个方格的端点分为四个小端点点,在不穿过最短路的小端点之间连边权为 0 的边,方格上相邻的小端点连原边权,从左上角的一个小端点出发跑到左上角的另一个小端点即可

代码

#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;
typedef long long ll;
const ll Inf=1ll<<62;
const int N=410;
struct Graph {
    int v,nx;
    ll w;
}g[16*N*N];
int cnt,list[4*N*N],fa[4*N*N];//0times:rightup,1times:leftup,2times:leftdown,3times:rightdown
ll w1[N][N],w2[N][N],dis[4*N*N];
int n,m,sum;
struct Path {
    int u;ll dis;
    friend bool operator < (Path a,Path b) {return a.dis>b.dis;}
};
bool vis[4*N*N],pth[N*N][4],vil[N][N];//0:down,1:up,2:left,3:right

void Add(int u,int v,ll w) {g[++cnt]=(Graph){v,list[u],w};list[u]=cnt;g[++cnt]=(Graph){u,list[v],w};list[v]=cnt;}

int id(int i,int j) {return (i-1)*(m+1)+j;}

void Dijkstra(int v0) {
    priority_queue<Path> q;
    while (!q.empty()) q.pop();
    memset(vis,0,sizeof vis);
    for (int i=1;i<=4*sum;i++) dis[i]=Inf;
    q.push((Path){v0,dis[v0]=0});
    while (!q.empty()) {
        Path u=q.top();q.pop();
        if (vis[u.u]) continue;vis[u.u]=1;
        for (int i=list[u.u];i;i=g[i].nx)
            if (dis[g[i].v]>dis[u.u]+g[i].w) q.push((Path){g[i].v,dis[g[i].v]=dis[fa[g[i].v]=u.u]+g[i].w});
    }
}

int main() {
    scanf("%d%d",&n,&m);sum=(n+1)*(m+1);
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m;j++) scanf("%d",&vil[i][j]);
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m+1;j++) scanf("%lld",&w1[i][j]),Add(id(i,j),id(i+1,j),w1[i][j]);
    for (int i=1;i<=n+1;i++)
        for (int j=1;j<=m;j++) scanf("%lld",&w2[i][j]),Add(id(i,j),id(i,j+1),w2[i][j]);
    Dijkstra(1);
    cnt=0;memset(list,0,sizeof list);
    for (int i=1;i<=n;i++)
        for (int j=1,u;j<=m;j++)
            if (vil[i][j]) {
                u=id(i,j);
                while (u!=1) {
                    if (u-fa[u]==m+1) pth[u][0]=pth[fa[u]][1]=1;
                    if (u-fa[u]==-(m+1)) pth[fa[u]][0]=pth[u][1]=1;
                    if (u-fa[u]==1) pth[u][2]=pth[fa[u]][3]=1;
                    if (u-fa[u]==-1) pth[fa[u]][2]=pth[u][3]=1;
                    u=fa[u];
                }
            }
    for (int i=1;i<=n+1;i++)
        for (int j=1;j<=m+1;j++) {
            if (!(i==1&&j==1)&&!pth[id(i,j)][0]&&!vil[i-1][j-1]&&!vil[i-1][j]) Add(id(i,j),id(i,j)+sum,0);
            if (!(i==1&&j==1)&&!pth[id(i,j)][2]&&!vil[i-1][j-1]&&!vil[i][j-1]) Add(id(i,j),id(i,j)+3*sum,0);
            if (!pth[id(i,j)][1]&&!vil[i][j]&&!vil[i][j-1]) Add(id(i,j)+2*sum,id(i,j)+3*sum,0);
            if (!pth[id(i,j)][3]&&!vil[i][j]&&!vil[i-1][j]) Add(id(i,j)+sum,id(i,j)+2*sum,0);
            if (i<=n) Add(id(i,j)+3*sum,id(i+1,j),w1[i][j]),Add(id(i,j)+2*sum,id(i+1,j)+sum,w1[i][j]);
            if (j<=m) Add(id(i,j)+sum,id(i,j+1),w2[i][j]),Add(id(i,j)+2*sum,id(i,j+1)+3*sum,w2[i][j]);
        }
    Dijkstra(1+sum);
    printf("%lld\n",dis[1+3*sum]);
}
View Code

 

posted @ 2021-03-25 18:40  Vagari  阅读(83)  评论(0编辑  收藏  举报