試題 歷屆試題 郵局
阿新 • • 發佈:2020-09-02
資源限制
時間限制: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中,於是程式碼如下(超時)——
現在給出了m個備選的郵局,請從中選出k個來,使得村民到自己家最近的郵局的距離和最小。其中兩點之間的距離定義為兩點之間的直線距離。 輸入格式 輸入的第一行包含三個整數n, m, k,分別表示村民的戶數、備選的郵局數和要建的郵局數。
接下來n行,每行兩個整數x, y,依次表示每戶村民家的座標。
接下來m行,每行包含兩個整數x, y,依次表示每個備選郵局的座標。
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。 思路
1 #include <iostream> 2 #include <cstdio> 3 #include <cstdlib> 4 #include <cmath> 5 #include <algorithm> 6 #define INF 0x3f3f3f3f 7 #define zero 1e-7 89 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 }