1. 程式人生 > 其它 >P2498 [SDOI2012]拯救小云公主(並查集/Prim)

P2498 [SDOI2012]拯救小云公主(並查集/Prim)

題目傳送門

題意

給定n個boss的座標,英雄在左下角(1,1),公主在右上角(row,line),英雄決定找一條路徑使到距離boss的最短距離最遠。
Ps:英雄走的方向是任意的,但是不能走出矩形的範圍。即英雄可以到達矩形範圍內的任意一個點(沒有必要是整點)

輸入格式

n表示boss的數目,row,line表示矩形的大小;
接下來n行,每行分別兩個整數表示boss的位置座標。

輸出格式

輸出一個小數,表示英雄的路徑離boss的最遠距離,精確到小數點後兩位

樣例

input

1 3 3
2 2

output

1.00

思路

假設我們現在知道了答案為x,那麼我們顯然可以將n個boss所在的點轉化為n個半徑為x的圓(不包括圓的邊),那麼,也就是說我們可以有一條路徑從(1,1)走到(row,line)。所以,第一想法就是二分答案,然後得到所有圓,然後判斷能否從起點走到終點。
此時的問題就在於我們該如何判斷是否有解呢。
這裡有個很巧妙的思路就是,因為這個圖並不是只走整點,所以很難直接bfs幾個方向然後判斷是否存在一條路徑到達,於是,這裡的轉化就出現了,我們判斷從左上角是否可以通過這些圓(並查集維護)抵達右下角

,也就是說,只走障礙,看看能不能障礙直接將路隔開。
以上的方法是 O(n^2*log())
如果模擬過畫圓的感覺,其實我們可以有個比較巧妙的感覺,就是這個題的圖和最小生成樹有點像,就是從左上角和右下角聯通時的最小生成樹。並且這個圖是個稠密圖。於是,我們可以考慮跑一個裸的Prim演算法,時間複雜度為O(n^2)

code 該程式碼採用第二個方法

#include <bits/stdc++.h>
using  namespace  std;

typedef long long ll;
typedef unsigned long long ull;
//#pragma GCC optimize(3)
#define pb push_back
#define is insert
#define PII pair<int,int>
#define show(x) cerr<<#x<<" : "<<x<<endl;
//mt19937 mt19937random(std::chrono::system_clock::now().time_since_epoch().count());
//ll getRandom(ll l,ll r){return uniform_int_distribution<ll>(l,r)(mt19937random);}

const int INF=0x3f3f3f3f;//2147483647;
const int N=3050,M=1e5+50;
const ll mod=998244353;

int n,row,line;
struct node {
    double x,y;
}a[N];

double cal(int i,int j){
    return 0.5*sqrt((a[i].x-a[j].x)*(a[i].x-a[j].x)+(a[i].y-a[j].y)*(a[i].y-a[j].y));
}
double dis[N];
int vis[N];
double ans=0;
double g[N][N];
void prim(){
    for(int i=1;i<=n+2;i++){
        dis[i]=10000000000;
    }
    int s=n+1;
    for(int i=1;i<=n+2;i++){
        vis[s]=1;
        for(int j=1;j<=n+2;j++) {
            dis[j] = min(dis[j], g[j][s]);
        }
        double minn=10000000000,pos=-1;
        for(int j=1;j<=n+2;j++){
            if(vis[j])continue;
            if(minn>dis[j]){
                minn=dis[j];
                pos=j;
            }
        }
        if(minn!=10000000000){
            ans=max(minn,ans);
            s=pos;
            if(s==n+2){
                break;
            }
        }
    }
}
void solve() {
    cin>>n>>row>>line;
    for(int i=1;i<=n;i++){
        cin>>a[i].x>>a[i].y;
    }
    for(int i=1;i<=n+2;i++){
        for(int j=1;j<=n+2;j++){
            g[i][j]=10000000000;
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            g[i][j]=cal(i,j);
        }
    }
    for(int i=n+1;i<=n+2;i++){
        for(int j=1;j<=n;j++){
            if(i==n+1)g[i][j]=g[j][i]=min(a[j].x-1,line-a[j].y);
            if(i==n+2)g[i][j]=g[j][i]=min(row-a[j].x,a[j].y-1);
        }
    }
    prim();
    cout<<fixed<<setprecision(2)<<ans;
}

signed main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int __=1;//cin>>__;
    while(__--){
        solve();
    }
    return 0;
}

總結

如果是網格圖,左上到右下四連通(只能走上下左右)等價於左下邊和右上邊八連通(除了上下左右還可以走四種斜對角)

例如(S為起點,T為終點,!為障礙)
S0!
0!0
!0T
那麼, S 不能走到 T 等價於左下邊走障礙點可以八連通走到右上邊
(四和八交換也是成立的)