洛谷題單指南-搜尋-P1433 吃乳酪

江城伍月發表於2024-03-06

原題連結:https://www.luogu.com.cn/problem/P1433

題意解讀:計算經過所有乳酪一次的總路徑最短,可以採用dfs、dp等方法。

解題思路:

最直接的思路是DFS,暴搜所有的路徑方案,計算最小距離,n最大是15,複雜度為15!≈10^12,必定會超時,先保證正確性,得到部分分:

50分程式碼:

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

const int N = 20;

double x[N], y[N]; //儲存乳酪座標
int path[N]; //儲存路徑是第幾個乳酪
double jl[205][205]; //儲存每兩個點之間的距離
bool flag[N]; //標記第i個乳酪是否已走過
int n;

double ans = 2e9;

double distance(double x1, double y1, double x2, double y2)
{
    return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}

//k:第幾個乳酪,dist:當前走過的距離
void dfs(int k, double dist)
{
    if(k > n)
    {
        ans = min(ans, dist);
        return;
    }
    for(int i = 1; i <= n; i++)
    {
       if(flag[i]) continue;
       flag[i] = true;
       path[k] = i;
       dfs(k + 1, dist + jl[path[k]][path[k-1]]);
       flag[i] = false; //恢復flag
    }
}

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> x[i] >> y[i];

    for(int i = 0; i <= n; i++)
    {
        for(int j = 0; j <= n; j++)
        {
            jl[i][j] = distance(x[i], y[i], x[j], y[j]);
        }
    }

    dfs(1, 0);
    cout << fixed << setprecision(2) << ans;

    return 0;
}

能否儘量減少DFS的次數呢?由於是要找最小路徑,那麼如果在DFS過程中,某次已走過的路徑已經大於之前儲存的最小路徑,則本次dfs可以返回,這樣起到了一定的剪枝效果,關鍵程式碼是:if(dist > ans) return;

由於剪枝無法準確評估到底能減掉多少,所以複雜度是由資料點來決定的,仍然無法保證不超時,只能儘量減少超時。

90分程式碼:

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

const int N = 20;

double x[N], y[N]; //儲存乳酪座標
int path[N]; //儲存路徑是第幾個乳酪
double jl[205][205]; //儲存每兩個點之間的距離
bool flag[N]; //標記第i個乳酪是否已走過
int n;

double ans = 2e9;

double distance(double x1, double y1, double x2, double y2)
{
    return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}

//k:第幾個乳酪,dist:當前走過的距離
void dfs(int k, double dist)
{
    if(dist > ans) return; //如果某次中間的距離已經大於之前的最小ans,直接結束本次搜尋
    if(k > n)
    {
        ans = min(ans, dist);
        return;
    }
    for(int i = 1; i <= n; i++)
    {
       if(flag[i]) continue;
       flag[i] = true;
       path[k] = i;
       dfs(k + 1, dist + jl[path[k]][path[k-1]]);
       flag[i] = false; //恢復flag
    }
}

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> x[i] >> y[i];

    for(int i = 0; i <= n; i++)
    {
        for(int j = 0; j <= n; j++)
        {
            jl[i][j] = distance(x[i], y[i], x[j], y[j]);
        }
    }

    dfs(1, 0);
    cout << fixed << setprecision(2) << ans;

    return 0;
}

還能不能進一步減少dfs的次數呢?

想象一種場景,當最多有15個點時,如果已經過a、b、c、d、e 5個點,當前在d點,走過距離是L1

下一次dfs時,也已經過a、b、c、d、e 5個點,當前在b點,走過距離是L2,

如果L2>L1,其實這時就沒有必要繼續dfs下去,因為肯定不可能是最優解。

因此,我們可以記錄每種狀態下所走過的距離,透過二進位制來表示已經過了哪幾個點

如:01011可以表示經過了1/2/4號點,用整數表示就是11,15個點用整數表示狀態不超過2^16-1 = 65535

定義陣列double dp[40000][20]; dp[i][j]表示,已經過的點是i所表示的二進位制狀態(1表示走過,0表示沒走過)、當前在j點所經過的距離,

在程式碼中,透過判斷dp[i][j]是否有值,且當前值比原值還大,則不需要繼續dfs。

100分程式碼:

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

const int N = 20;

double x[N], y[N]; //儲存乳酪座標
int path[N]; //儲存路徑是第幾個乳酪
double jl[205][205]; //儲存每兩個點之間的距離
bool flag[N]; //標記第i個乳酪是否已走過
double dp[65540][20]; //dp[i][j]表示已經過的點是i所表示的狀態,當前在j點所經過的距離
int n;

double ans = 2e9;

double distance(double x1, double y1, double x2, double y2)
{
    return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}

//k:第幾個,s:當前已經過的點的狀態,dist:當前走過的距離
void dfs(int k, int s, double dist)
{
    if(dist > ans) return; //如果某次中間的距離已經大於之前的最小ans,直接結束本次搜尋
    if(k > n)
    {
        ans = min(ans, dist);
        return;
    }
   
    for(int i = 1; i <= n; i++) //遍歷所有乳酪編號
    {
        if(flag[i]) continue;
 
        int ns = s + (1 << (i - 1)); //當前已經過的點算上i 
        double ndist = dist + jl[i][path[k-1]]; //已經過的距離加上當前距離
        if(dp[ns][i] != 0 && ndist >= dp[ns][i]) continue; //如果當前已經過點的狀態之前已出現過,且距離比之前還大或相等,則不需要繼續dfs
        
        flag[i] = true; path[k] = i; dp[ns][i] = ndist;
        dfs(k + 1, ns, ndist);
        flag[i] = false; //恢復flag
    }
}

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> x[i] >> y[i];

    //預計算所有點之間的距離
    for(int i = 0; i <= n; i++)
    {
        for(int j = 0; j <= n; j++)
        {
            jl[i][j] = distance(x[i], y[i], x[j], y[j]);
        }
    }

    dfs(1, 0, 0);
    cout << fixed << setprecision(2) << ans;

    return 0;
}

相關文章