洛谷P2864

来一发在洛谷的第一篇题解

解析

首先从原点出发回到原点会形成一个环

要计算在环上的路程,首先我们要破环

利用建墙法破环

可以选取任意一个边缘上的树,往上或者往下或者往左往右建立一堵墙‘|’

这样利用bfs搜索的时候无法穿过这堵墙

如下所示我们在第一棵树上面建了一堵墙,隔绝了左右两个部分

...|...
...|...
...X...
..XXX..
...XXX.
...X...
......*

从起点到相邻着墙左边的点的最短距离+到相邻着墙右边点的最短距离+在墙上走的距离之和(因为实际上我们还是要走墙这一段距离),求其最小值就是我们需要的答案

解法正确性证明

假设我们已经通过这种方法求出来一个解,存在比这种解法更优的解法

首先我们知道这种更优的解法也一定会穿过墙,并且对应墙相邻左右的两个点

我们的这种解法其中一种情况也同样对应墙两边相邻左右的两个点,并且与更优解的这两个点对应

(原解法)
设x1是起点到墙左边这个点的距离,x2是到右边点的距离,x3是中间在墙上经过的距离

(更优解)
x11与x22,x33和上面x1,x2,x3定义一样,

那么一定有x1 + x2 < x11 + x22

如果更优解存在,那么x3 > x33

说明在墙上的距离存在比x3更小

然而我们所想的方法求得是x1 + x2 + x3 的最小值

如果x33 比x3更小,那么显然x1+x2+x33才是我们所想方法的解

这和假设相矛盾

即可得证

代码

#include<iostream>
#include<utility>
#include<queue>
#include<cmath>
using namespace std;
typedef long long ll;
#define fi(i,a,b) for(int i = a; i <= b; ++i)
#define fr(i,a,b) for(int i = a; i >= b; --i)
#define x first
#define y second
#define sz(x) ((int)(x).size())
#define pb push_back
using pii = pair<int,int>;
//#define DEBUG
int dis[55][55];//最短路数组
char forest[55][55];//样例数据
bool vis[55][55];//标记是否访问过
int qx,qy;//第一棵树
int sx,sy;//起始点
int n,m;
int res = 0x3f3f3f3f;//答案
int dx[8] = {1,1,1,0,0,-1,-1,-1};
int dy[8] = {1,0,-1,1,-1,1,0,-1};//方向数组
struct point{
    int x,y,k;
};//分别表示坐标和最短路
void bfs(int x,int y){
    queue<point> que;
    que.push({sx,sy,0});
    vis[sx][sy] = true;
    while(!que.empty()){
        point temp = que.front();
        que.pop();
        fi(i,0,7){
            int p = temp.x + dx[i];
            int q = temp.y + dy[i];
            if(p >= 1 && p <= n && q >= 1 && q <= m && forest[p][q] == '.' && !vis[p][q]){
                vis[p][q] = true;
                dis[p][q] = temp.k + 1;//bfs第一个访问到得就是最短路
                que.push({p,q,dis[p][q]});
            }
        }
    }
    return;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n >> m;
    int bk = 0;
    fi(i,1,n) fi(j,1,m) {
        cin >> forest[i][j];
        if(forest[i][j] == 'X' && !bk){
            qx = i,qy = j;
            bk = 1;
        }//标记第一颗树
        else if(forest[i][j] == '*'){
            sx = i,sy = j;
        }
    }

    fi(i,1,qx-1){
        forest[i][qy] = '|';
    } //把第一棵树上面得所有点变成墙

    bfs(sx,sy);
    
    fi(i,1,qx) fi(j,1,qx){//循环到qx因为可以斜着走
        if(forest[i][qy-1] == '.' && forest[j][qy+1] == '.')//循环墙得左右两边得点
            res = min(dis[i][qy-1] + dis[j][qy+1] + abs(i-j) + 1,res);
    }
    cout << res +1 << endl;//加上原点
#ifdef DEBUG
    //freopen(D:\in.txt,r,stdin);
#endif
    return 0;
}
posted @ 2022-02-26 17:25  Sun-Wind  阅读(48)  评论(0编辑  收藏  举报