题目:
描述
一个街区有很多住户,街区的街道只能为东西、南北两种方向。
住户只可以沿着街道行走。
各个街道之间的间隔相等。
用(x,y)来表示住户坐在的街区。
例如(4,20),表示用户在东西方向第4个街道,南北方向第20个街道。
现在要建一个邮局,使得各个住户到邮局的距离之和最少。
求现在这个邮局应该建在那个地方使得所有住户距离之和最小;
输入
第一行一个整数n<20,表示有n组测试数据,下面是n组数据;
每组第一行一个整数m<20,表示本组有m个住户,下面的m行每行有两个整数0<x,y<100,表示某个用户所在街区的坐标。
m行后是新一组的数据;
输出
每组数据输出到邮局最小的距离和,回车结束;
样例输入
2
3
1 1
2 1
1 2
5
2 9
5 20
11 9
1 1
1 20
样例输出
2
44
解决方法:
下列方法一使用的是暴力枚举,方法二使用的哈夫曼距离求解,都AC通过了。
方法一:
主要思想:
暴力枚举,可以把住户坐落的街区(x, y)当成一个100*100的格子,用户和邮局在格子的交点处,则遍历每一个格子的交点,算法总距离的最小值。因为如果有m个点,这m个点的x值和y值最大的是maxX和maxY,则我们的x和y只需要遍历到maxX和maxY即可,这点可以使得maxX和maxY小的case执行时间小些至于输入坐标的存放可以使用一维数组或者二维数组。
附:可以初步估算一下暴力枚举的时间,看看是不是超时。因为输入的0<x,y<100,所以遍历x和y最后100*100=10000次(将每一个点都当做是最终使得距离和最小的点),又因为m<20,所以对于任一点,最多需要循环20次,将每一个点和暂时当做的最终点求距离,因此共20*10000=2*10^5次,通常1秒时间可以执行10^8次,因此题目限制时间3000ms=3s是不会超时的(来自匡同学的耐心指导,我什么都不会--~~)
代码如下:
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
//*****************下面是方法1:暴力枚举******************
//求距离,
//参数maxX和maxY代表输入的n个住户中最大的x坐标和最大的y坐标
//selectedX、selectedY表示当做使得距离和最短的点坐标,x和y表示输入数据,m表示输入数据个数
int distance(int selectedX, int selectedY, int *x, int *y, int m)
{
int sum = 0;
for (int i = 0; i < m; i++)
{
sum = sum + fabs(selectedX - x[i]) + fabs(selectedY - y[i]);
}
return sum;
}
int main()
{
int n, m;
int maxX = 0, maxY = 0;
cin.sync_with_stdio(false);
cin >> n;
while((n--)>0)
{
//初始化住户数组
//借助二维数组,x存储所有x的值,y存储所有y的值
cin >> m;
int *x = new int[m];
int *y = new int[m];
for(int i = 0; i < m; i++)
{
//读入数据,并得到出现的住户中的最大的x值和y值
cin >> x[i] >> y[i];
if(maxX < x[i])
{
maxX = x[i];
}
if(maxY < y[i])
{
maxY = y[i];
}
}
int sum = 1e9;
//暴力枚举。当住户的x和y较大时,效率非常低
for(int i = 1; i <= maxX; i++)
{
for (int j = 1; j <= maxY; j++)
{
int temp = distance(i, j, x, y, m);
if(temp <= sum)
{
sum = temp;
}
}
}
cout << sum << endl;
delete []x;
delete []y;
}
return 0;
}
最终执行结果:
时间:36 内存:312
匡同学的代码(悄悄附上,感谢指导~):
#include <bits/stdc++.h>
using namespace std;
struct node{
int x,y ;
}nod[50];
int n , m;
int solve(int x,int y){
int ans = 0 ;
for(int i = 0 ;i<n;i++){
ans= ans + abs(x-nod[i].x)+abs(y-nod[i].y) ;
}
return ans ;
}
int main()
{
int t ;
cin>>t ;
while(t--){
cin>>n ;
for(int i = 0 ;i<n;i++){
scanf("%d%d",&nod[i].x,&nod[i].y) ;
}
int ans = 1e9 ;
for(int i = 1;i<100 ;i++){
for(int j = 1;j<100;j++){
ans = min(ans,solve(i,j)) ;
}
}
cout<<ans<<endl;
}
return 0;
}
最终执行结果:
时间:24 内存:224
方法二:
主要思想(摘自参考文献,加了一点自己的理解):
1、距离和最小的邮局点一定设在某个居民点上(这个具体解释下,感谢下面评论的同学指出的错误!这句话意思的真实理解是把x和y轴单独考虑,也就是邮局点的x一定是居民点x集合中的一个,邮局点的y也一定是居民点的y值中的一个),否则邮局到其他任何非居民点的距离总会比最小距离多出来一段,这一段就是非居民点的那些点到离他最近的居民点的距离
2、为求x轴距离和y轴距离之和可以转化为分别求到x轴距离和到y轴距离。
3、以x轴横向点到其他最短距离为例,问题等价于在x轴有若干点选取其中一点,使该点到其他点距离之和最小
4、这个最短距离点一定是排在中间的,若是奇数个点是最中间的一个,若是偶数个点则是最中间的2个之一(可以用下面画图的方法验证,其实在中间两个点及两个点中间的任意处,点到居民的距离和都是相等的),关于这个的理解可以参见下图:
因此,就将求距离和最短的问题转化为了求中间点到其他所有点距离之和的问题。
代码如下:
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
//下面是方法二,将其转化为求一个点到其余点的距离最小值
bool compare(int a, int b)
{
return a < b; //升序排列,若改为a>b,则为降序排列
}
int main()
{
int n, m;
cin >> n;
while( n-- )
{
cin >> m;
int *x = new int[m];
int *y = new int[m];
for (int i = 0;i < m; i++)
{
cin >> x[i] >> y[i];
}
sort(x, x+m, compare);
sort(y, y+m, compare);
int sum = 0;
for (int i = 0; i < m/2; i++)
{
sum = sum + (x[m-i-1] - x[i]) + (y[m-i-1] - y[i]);
}
cout << sum << endl;
delete []x;
delete []y;
}
}
执行结果:
时间:0 内存:312
总结
想要代码执行速度快,想办法躲避各种循环,另外代码风格,注意优化~
参考文章:http://blog.chinaunix.net/uid-28800073-id-3581442.html