1. 程式人生 > 實用技巧 >試題 歷屆試題 郵局

試題 歷屆試題 郵局

題目連結

資源限制 時間限制:1.0s 記憶體限制:256.0MB 問題描述   C村住著n戶村民,由於交通閉塞,C村的村民只能通過信件與外界交流。為了方便村民們發信,C村打算在C村建設k個郵局,這樣每戶村民可以去離自己家最近的郵局發信。

  現在給出了m個備選的郵局,請從中選出k個來,使得村民到自己家最近的郵局的距離和最小。其中兩點之間的距離定義為兩點之間的直線距離。 輸入格式   輸入的第一行包含三個整數n, m, k,分別表示村民的戶數、備選的郵局數和要建的郵局數。
  接下來n行,每行兩個整數x, y,依次表示每戶村民家的座標。
  接下來m行,每行包含兩個整數x, y,依次表示每個備選郵局的座標。
  在輸入中,村民和村民、村民和郵局、郵局和郵局的座標可能相同,但你應把它們看成不同的村民或郵局。 輸出格式   輸出一行,包含k個整數,從小到大依次表示你選擇的備選郵局編號。(備選郵局按輸入順序由1到m編號) 樣例輸入 5 4 2
0 0
2 0
3 1
3 3
1 1
0 1
1 0
2 1
3 2 樣例輸出 2 4 資料規模和約定   對於30%的資料,1<=n<=10,1<=m<=10,1<=k<=5;
  對於60%的資料,1<=m<=20;
  對於100%的資料,1<=n<=50,1<=m<=25,1<=k<=10。 思路
  這題首先想到的就是dfs,每個郵局都有兩個狀態:選或不選,當選擇的郵局數剛好為k個時,求出最小距離和,與ans比較,若比ans更小,那麼就將選擇的這組郵局記錄在陣列flag中,於是程式碼如下(超時)——
 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstdlib>
 4 #include <cmath>
 5 #include <algorithm>
 6 #define INF 0x3f3f3f3f
 7 #define zero 1e-7
 8
9 using namespace std; 10 typedef long long ll; 11 12 struct node { 13 int x, y; 14 }cm[55], yj[30];//村民、郵局的座標 15 16 int n, m, k;//村民戶數,備選郵局數,要建的郵局數 17 double d[55][30];//村民到各郵局的距離 18 bool vis[30];//標記郵局是否被選擇 19 double ans=INF;//最小距離和 20 int flag[15];//記錄選擇的郵局 21 22 void init() { 23 for(int i=0; i<n; i++) 24 cin>>cm[i].x>>cm[i].y; 25 for(int i=0; i<m; i++) 26 cin>>yj[i].x>>yj[i].y; 27 for(int i=0; i<n; i++) 28 for(int j=0; j<m; j++) 29 d[i][j]=sqrt((cm[i].x-yj[j].x)*(cm[i].x-yj[j].x)+(cm[i].y-yj[j].y)*(cm[i].y-yj[j].y)); 30 } 31 32 void dfs(int t, int cnt) {//考慮第t個郵局,當前選出的郵局數 33 if(cnt==k) {//若已選出k個郵局 34 double sum=0; 35 for(int i=0; i<n; i++) { 36 double mind=INF;//村民i到郵局的最短距離 37 for(int j=0; j<t; j++) { 38 if(vis[j] && d[i][j]<mind) 39 mind=d[i][j]; 40 } 41 sum+=mind; 42 } 43 if(sum<ans) {//若當前最短距離和小於ans,則更新ans和flag[] 44 int c=0; 45 for(int i=0; i<t; i++) { 46 if(vis[i]) flag[c++]=i+1; 47 } 48 ans=sum; 49 } 50 return; 51 } 52 if(m-t==k-cnt) {//若剛好剩下k-cnt個郵局,則必須全都選上 53 for(int i=t; t<m; t++) vis[i]=true; 54 dfs(m, k); 55 for(int i=t; t<m; t++) vis[i]=false;//回溯 56 return; 57 } 58 vis[t]=true; 59 dfs(t+1, cnt+1);//選擇郵局t 60 vis[t]=false;//回溯 61 dfs(t+1, cnt);//不選擇郵局t 62 } 63 64 int main() { 65 cin>>n>>m>>k; 66 init(); 67 dfs(0, 0); 68 for(int i=0; i<k; i++) { 69 cout<<flag[i]<<' '; 70 } 71 return 0; 72 }

  這樣暴力深搜的結果就是最後兩組測試用例超時了,只有80分,那麼就需要再進行剪枝. 慚愧的是,我不知道該怎麼剪了。。。於是只好百度求助各路大神,最後敲中這位博主的詳解,循序漸進,步步升級,誰都能看懂(๑•̀ㅂ•́)و✧→click here.

經過點撥,便發現還可以做如下優化:

1. 維護一個記錄 居民到已選郵局的最短距離 的陣列minlen[n];

2. 維護一個記錄 當前已選郵局的向量(vector)v;還需設定一個外部向量(vector)flag 以記錄最優組合方案;

3. 用陣列vis[]標記不能起到縮小距離作用的郵局;

  先完成前兩步優化,程式碼如下——

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstdlib>
 4 #include <cmath>
 5 #include <vector>
 6 #include <algorithm>
 7 #define INF 0x3f3f3f3f
 8 #define zero 1e-7
 9 
10 using namespace std;
11 typedef long long ll;
12 
13 struct node {
14     int x, y;
15 }cm[55], yj[30];//村民、郵局的座標
16 
17 int n, m, k;//村民戶數,備選郵局數,要建的郵局數
18 double d[55][30];//村民到各郵局的距離
19 double ans=INF;//最小距離和
20 vector<int> flag;//儲存最優選擇方案
21 
22 void init() {
23     for(int i=0; i<n; i++)
24         cin>>cm[i].x>>cm[i].y;
25     for(int i=0; i<m; i++)
26         cin>>yj[i].x>>yj[i].y;
27     for(int i=0; i<n; i++)
28         for(int j=0; j<m; j++)
29             d[i][j]=sqrt((cm[i].x-yj[j].x)*(cm[i].x-yj[j].x)+(cm[i].y-yj[j].y)*(cm[i].y-yj[j].y));
30 }
31 //考慮第t個郵局,當前選出的郵局數,當前儲存的居民到郵局的最短距離,當前已選擇的郵局
32 void dfs(int t, int cnt, double minlen[], vector<int> v) {
33     if(cnt==k) {//若已選出k個郵局
34         double sum=0;
35         for(int i=0; i<n; i++) {
36             sum+=minlen[i];
37         }
38         if(sum<ans) {//若當前最短距離和小於ans,則更新ans和flag
39             ans=sum;
40             flag=v;
41         }
42         return;
43     }
44     if(m-t<k-cnt) return;//選不夠k個郵局了,直接返回
45     if(t<m && cnt<k) {//當前郵局存在 且 還未選滿
46         double temp[55];
47         memcpy(temp, minlen, sizeof(temp));
48         bool ok=false;
49         for(int i=0; i<n; i++) {
50             if(d[i][t]<temp[i]) {
51                 temp[i]=d[i][t];
52                 ok=true;
53             }
54         }
55         //選擇郵局t
56         if(ok) {
57             v.push_back(t+1);
58             dfs(t+1, cnt+1, temp, v);
59             v.pop_back();//回溯
60         }
61         //不選擇郵局t
62         dfs(t+1, cnt, minlen, v);
63     }
64 }
65 
66 int main() {
67     cin>>n>>m>>k;
68     init();
69     double minlen[55];
70     for(int i=0; i<n; i++) minlen[i]=INF;
71     vector<int> v;
72     dfs(0, 0, minlen, v);
73     for(int i=0; i<k; i++) {
74         cout<<flag[i]<<' ';
75     }
76     return 0;
77 }

  到這步就90分了,還有最後一組測試用例超時沒過,接著再進行第三步優化.

  於是修改後程式碼如下(不知為何,設定為先 不選擇郵局t 進行深搜後就會錯,實在找不出原因來,下面的程式碼是錯的,一個測試用例都過不了,如有路過的大神能指點迷津,實在感激不盡~~~)——

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstdlib>
 4 #include <cmath>
 5 #include <vector>
 6 #include <algorithm>
 7 #define INF 0x3f3f3f3f
 8 #define zero 1e-7
 9 
10 using namespace std;
11 typedef long long ll;
12 
13 struct node {
14     int x, y;
15 }cm[55], yj[30];//村民、郵局的座標 
16 
17 int n, m, k;//村民戶數,備選郵局數,要建的郵局數 
18 double d[55][30];//村民到各郵局的距離 
19 bool vis[30];//標記郵局是否起到縮小距離作用 
20 double ans=INF;//最小距離和 
21 vector<int> flag;//儲存最優選擇方案 
22 
23 void init() {
24     for(int i=0; i<n; i++) 
25         cin>>cm[i].x>>cm[i].y;
26     for(int i=0; i<m; i++) 
27         cin>>yj[i].x>>yj[i].y;
28     for(int i=0; i<n; i++) 
29         for(int j=0; j<m; j++) 
30             d[i][j]=sqrt((cm[i].x-yj[j].x)*(cm[i].x-yj[j].x)+(cm[i].y-yj[j].y)*(cm[i].y-yj[j].y));
31 }
32 //考慮第t個郵局,當前選出的郵局數,當前儲存的居民到郵局的最短距離,當前已選擇的郵局 
33 void dfs(int t, int cnt, double minlen[], vector<int> v) { 
34     if(cnt==k) {//若已選出k個郵局 
35         double sum=0;
36         for(int i=0; i<n; i++) 
37             sum+=minlen[i];
38         if(sum<ans) {//若當前最短距離和小於ans,則更新ans和flag 
39             ans=sum;
40             flag=v;
41         }
42         return;
43     }
44     if(m-t<k-cnt) return;//選不夠k個郵局了,直接返回 
45     if(t<m) { 
46         //不選擇郵局t
47         dfs(t+1, cnt, minlen, v);
48         if(vis[t]) return;//若郵局t已標記為不可選,那麼直接return 
49         bool ok=false;
50         for(int i=0; i<n; i++) {
51             if(d[i][t]<minlen[i]) {
52                 minlen[i]=d[i][t];
53                 ok=true;
54             }
55         }
56         //選擇郵局t
57         if(ok) {
58             v.push_back(t+1);
59             dfs(t+1, cnt+1, minlen, v);
60             v.pop_back();//回溯 
61         }
62         else vis[t]=true;//標記為無法起到縮小距離作用,即不可選狀態 
63     }
64 }
65 
66 int main() {
67     cin>>n>>m>>k;
68     init();
69     double minlen[55];
70     for(int i=0; i<n; i++) minlen[i]=INF; 
71     vector<int> v;
72     dfs(0, 0, minlen, v);
73     for(int i=0; i<k; i++) 
74         cout<<flag[i]<<' ';
75     return 0;
76 }