1. 程式人生 > >Picnic Planning 【POJ - 1639】【Kruskal+有限度最小生成樹】

Picnic Planning 【POJ - 1639】【Kruskal+有限度最小生成樹】

題目連結


  題意:若干個人開車要去park聚會,但是park能停的車是有限的,為k。所以這些人要通過先開車到其他人家中,停車,然後拼車去聚會。另外,車的容量是無限的,他們家停車位也是無限的。求開車總行程最短。
  就是求一最小生成樹,但是對於其中一個點其度不能超過k。


  接下來,就是怎麼處理最小生成樹的建立問題了,對於這樣的樹,我們可以看作是除去中間的park節點以外,其他所有的節點先各自構成最小生成樹,得到M個聯通分塊,然後再用M個聯通分塊與park節點連線,那麼,此時park已經用掉了M個度,那麼,對於題目要求:一定存在解,就表明了park的限制度是大於等於M的。那麼,接下來,我們就得處理多出來的部分(如果有的話)。

  對於多出來的部分,我們有這樣的選擇:

  • 選擇將park與這點連線,並且放棄它們構成的環內的其中一條邊,顯然,得放棄最大邊;
  • 不選擇這條邊,說明已經到了最優解,沒法再放棄其他邊了。

  那麼,我們要怎麼確定要選擇的邊,與銜接上去的邊呢?對於每一個由park節點出發的節點,我們能通過dfs()搜到它如果與park形成環的話,環內部節點的邊的最大值,若是把它與park連線起來的話,就是要刪除一條最大鏈,並且增加上它與park的距離。

  我們重複上面的操作,直到找不到合適的值,使得刪除樹上的點再新增上新的點的時候距離更小。


#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <limits>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#define lowbit(x) ( x&(-x) )
#define pi 3.141592653589793
#define e 2.718281828459045
#define INF 0x3f3f3f3f
#define efs 1e-6
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
const int maxP = 30;
const int maxN = 1005;
const int park = 1;  //公園一定是1
int N, cnt, limit, root[maxP], sum, mp[maxP][maxP];
bool inTree_line[maxP][maxP];   //這兩個點在樹上,且直接相連
string s1, s2;
map<string, int> ms;
map<string, bool> sr;
struct Eddge
{
    int u, v, val;
    Eddge(int a=0, int b=0, int c=-1):u(a), v(b), val(c) {}
}edge[maxN], dp[maxN];
bool cmp(Eddge e1, Eddge e2) { return e1.val < e2.val; }
int fid(int x) { return x==root[x]?x:(root[x] = fid(root[x])); }
void Kruskal()
{
    int inque_line = 0;
    for(int i=1; i<=N; i++)
    {
        if(inque_line >= cnt - 2) break;    //去掉的節點是park節點
        if(edge[i].u == park || edge[i].v == park) continue;
        int u = fid(edge[i].u), v = fid(edge[i].v);
        if(u != v)
        {
            root[u] = v;
            inque_line++;
            sum += edge[i].val;
            inTree_line[edge[i].u][edge[i].v] = inTree_line[edge[i].v][edge[i].u] = true;
        }
    }
}
void dfs(int u, int fa)
{
    for(int i=2; i<=cnt; i++)
    {
        if(i == fa || !inTree_line[u][i]) continue;
        if(dp[i].val == -1)
        {
            if(dp[u].val > mp[u][i]) dp[i] = dp[u];
            else dp[i] = Eddge(i, u, mp[u][i]);
        }
        dfs(i, u);
    }
}
void solve()
{
    int key_P[maxP], min_to_root[maxP];   //key_P[]知道,對應連線的每個聯通分塊中的節點,以及每個聯通分塊到根的最短距離
    memset(min_to_root, INF, sizeof(min_to_root));
    for(int i=2; i<=cnt; i++)
    {
        if(mp[i][park] < INF)
        {
            int the_R = fid(i);
            if(min_to_root[the_R] > mp[i][park])
            {
                min_to_root[the_R] = mp[i][park];
                key_P[the_R] = i;
            }
        }
    }
    int M = 0;  //M個聯通分塊的處理
    for(int i=2; i<=cnt; i++)
    {
        if(min_to_root[i] < INF)
        {
            sum += min_to_root[i];
            M++;
            inTree_line[key_P[i]][park] = inTree_line[park][key_P[i]] = true;
        }
    }
    for(int i=M+1; i<=limit; i++)
    {
        memset(dp, -1, sizeof(dp));     //dp處理的是最長路徑,然後比對最長路以及替換到根節點,是否可以減少需求
        dp[park].val = -INF;   //不能選擇根節點,所以賦最小值
        dfs(park, -1); //從根節點出發
        int Minn = -INF, pos = 0;
        for(int j=2; j<=cnt; j++)
        {
            if(Minn < dp[j].val - mp[j][park])
            {
                Minn = dp[j].val - mp[j][park];
                pos = j;
            }
        }
        if(Minn <= 0) break;
        inTree_line[park][pos] = inTree_line[pos][park] = true;
        inTree_line[dp[pos].u][dp[pos].v] = inTree_line[dp[pos].v][dp[pos].u] = false;
        sum -= Minn;
    }
}
void init()
{
    sum = 0;
    cnt = 1;
    ms.clear(); sr.clear();
    ms["Park"] = 1;
    sr["Park"] = true;
    for(int i=1; i<maxP; i++) root[i] = i;
    memset(mp, INF, sizeof(mp));
    memset(inTree_line, false, sizeof(inTree_line));
}
int main()
{
    while(scanf("%d", &N)!=EOF)
    {
        init();
        for(int i=1; i<=N; i++)
        {
            cin>>s1>>s2;
            if(!sr[s1]) ms[s1] = ++cnt; //cnt記錄了最後有多少人
            if(!sr[s2]) ms[s2] = ++cnt;
            sr[s1] = sr[s2] = true;
            int e1;
            scanf("%d", &e1);
            mp[ms[s1]][ms[s2]] = mp[ms[s2]][ms[s1]] = e1;
            edge[i] = Eddge(ms[s1], ms[s2], e1);
        }
        scanf("%d", &limit);
        sort(edge+1, edge+1+N, cmp);
        Kruskal();
        solve();
        printf("Total miles driven: %d\n", sum);
    }
    return 0;
}
/*
12
Park 4 84
Park 3 85
Park 6 11
Park 2 64
Park 5 57
Park 1 40
5 6 64
1 6 83
2 4 70
4 3 26
3 1 55
6 3 56
3
Accept ans = 259
*/

 

後面有一組測試資料,過了這個就基本是過了的,還有,一會我再用Prime寫一遍,挺好的一種想法。