请往下查看 2017-09-24 修改版
问题描述
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。
【资源约定】
峰值内存消耗 < 64M
CPU消耗 < 2000ms
本题我用的是深搜加剪枝,利用w[55]数组记录每一步所有用户到已选中的所有邮局中的最小(min)距离,当再次选中一个邮局时,倘若每一用户到此邮局的距离都比w数组里的大,则此邮局不能提供某一用户的最小距离,以后所有计算中都将此邮局舍弃,即剪枝,用标志位的方法舍弃此邮局
代码如下(代码未整理,供参考)
#include<iostream> //邮局
#include<stdlib.h>
#include<math.h>
using namespace std;
int n,m,k,j,c[55][2],y[27][2],d[12],f1,f2,f[55]={0};
float yc[27][55],s=1000000000;
int dfs(int t,int i,int o[12],float w[55],float sum)
{
if(i<=m+1)
{
if(t==k)
{
if(sum<s)
{
s=sum;
for(j=0;j<k;j++)
d[j]=o[j];
}
}
else if(i<=m&&t<k)
{
float ww[55];
for( j=1;j<=n;j++)
ww[j]=w[j];
dfs(t,i+1,o,w,sum);f1=1,f2=0;
if(!f[i])
{
o[t]=i;
if(t>0)
{
f2=1;
for( j=1;j<=n;j++)
{
if(ww[j]>yc[i][j])
{
sum=sum-ww[j]+yc[i][j];
ww[j]=yc[i][j];
f1=0;
}
}
}
else
{
for( j=1;j<=n;j++)
{
sum+=yc[i][j];
ww[j]=w[j]=yc[i][j];
}
}
if(f1&&f2)
{
f[i]=1;
dfs(t,i+1,o,w,sum);
}
else
dfs(t+1,i+1,o,ww,sum);
}
}
}
}
int main()
{
int i,j,o[12];
float w[55],ww[55];
cin>>n>>m>>k;
for(i=1;i<=n;i++)
cin>>c[i][0]>>c[i][1];
for(i=1;i<=m;i++)
{
cin>>y[i][0]>>y[i][1];
for(j=1;j<=n;j++)
yc[i][j]=sqrt((c[j][0]-y[i][0])*(c[j][0]-y[i][0])+(c[j][1]-y[i][1])*(c[j][1]-y[i][1]));
}
dfs(0,1,o,w,0);
for(i=0;i<k;i++)
cout<<d[i]<<" ";
return 0;
}
2017-09-24 修正版
本人工作了, 所以很少登录博客. 偶然看到评论里说代码没AC, 特来修改一下blog. 上面我解释过: 若某备选邮局不能提供某一用户的最小距离, 则剪枝, 但存在一种情况, 就是依次正序访问的备选邮局都能提供最小距离, 此时程序无法做出剪枝处理, 蓝桥杯官方大概就是这样修改了评测用例. 解决方案, 我们可以逆序访问备选邮局, 这样就可以剪枝了. 大家可以看看此次代码和之前的代码, 我修改的核心就是int main()里的 dfs 第二个参数. 所以, 我并没有真正的解决问题, 蓝桥杯官方还可以再把评测用例的顺序改回来, 这样我之前的代码或许有效. 如此, 解决方案大家去想吧. 如今看以前的代码结构真是特烂, 无心改, 大家将就着看吧.
剪枝描述
假设我们选中了3个邮局, 每户人家到邮局的最短距离都是1000米, 此时下一个备选邮局到最近居民点的距离是2000米, 这就是不能提供最短距离, 可以直接剪枝; 反例: 下一个备选邮局到最近居民点的距离是999米, 它能提供最短距离, 暂时不能剪枝, 再下一个, 998米, 997米... 这样我们就无法剪枝, 程序一直深搜下去, 结果就超时了, 所以, 我们可以逆序访问备选邮局, 这时, 已选中的邮局到每户人家的距离是997米, 下一个备选邮局是998米, 可以剪枝, 再下一个, 999米...
附测评详情:
#include<iostream>
#include<math.h>
/*
* 邮局
* 代码真烂, 这变量名都是啥, 现在连我本人都看不下去了;
* 大家忍忍吧...
*/
using namespace std;
int n, m, k, j, c[55][2], y[27][2], retArr[12], f1, f2, f[55] = {0};
float yc[27][55], minCount = 1000000000;
int dfs(int t, int i, int o[12], float w[55], float nowCount) {
if (i >= 0) {
if (t == k) {
if (nowCount < minCount) {
minCount = nowCount;
for (j = 0; j < k; j++) {
retArr[j] = o[j];
}
}
} else if (i > 0 && t < k) {
float ww[55];
for (j = 1; j <= n; j++) {
ww[j] = w[j];
}
dfs(t, i - 1, o, w, nowCount);
f1 = 1, f2 = 0;
if (!f[i]) {
o[t] = i;
if (t > 0) {
f2 = 1;
for (j = 1; j <= n; j++) {
if (ww[j] > yc[i][j]) {
nowCount = nowCount - ww[j] + yc[i][j];
ww[j] = yc[i][j];
f1 = 0;
}
}
} else {
for (j = 1; j <= n; j++) {
nowCount += yc[i][j];
ww[j] = w[j] = yc[i][j];
}
}
if (f1 && f2) {
f[i] = 1;
dfs(t, i - 1, o, w, nowCount);
} else {
dfs(t + 1, i - 1, o, ww, nowCount);
}
}
}
}
}
int main() {
int i, o[12];
float w[55];
cin >> n >> m >> k;
for (i = 1; i <= n; i++) {
cin >> c[i][0] >> c[i][1];
}
for (i = 1; i <= m; i++) {
cin >> y[i][0] >> y[i][1];
}
for (i = 1; i <= m; i++) {
for (j = 1; j <= n; j++) {
yc[i][j] = sqrt((c[j][0] - y[i][0]) * (c[j][0] - y[i][0]) + (c[j][1] - y[i][1]) * (c[j][1] - y[i][1]));
}
}
dfs(0, m, o, w, 0);
for (i = k - 1; i >= 0; i--) {
cout << retArr[i] << " ";
}
}