Picnic Planning 【POJ - 1639】【Kruskal+有限度最小生成樹】
阿新 • • 發佈:2018-12-25
題目連結
題意:若干個人開車要去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寫一遍,挺好的一種想法。