题目链接
题目大意
简要题意是找到一个位置,使得其它所有位置上的数乘以两个位置之间的距离的总和最小。
题目思路
前言
看到题目感觉不在我的知识范围内。。只会暴力 放一份暴力代码
for (int x = 1; x <= n; x++)
for (int y = 1;y <= m; y++){//枚举仓库的位置(x,y)
sum = 0;
for (int i = 1;i <= n; i++)
for (int j = 1;j <= m; j++)
sum += mp[i][j] * (abs(x - i) + abs(y - j));//计算与每一个地点的距离再乘上要去的次数,简单模拟题意即可
ans = min(ans, sum);
}
二维前缀和
在暴力方法一中每次枚举一个坐标都要把到所有点的距离重新算一遍,其实没必要。
若仓库在点 (x,y) 处,现在将仓库移动到点 (x+1,y) ,那么对于左上角为(1,1),右下角为(x,m)的矩阵,所有点到达仓库的距离都+1,对于左上角为(x+1,1),右下角为(n,m)的矩阵,所有点到达仓库的距离都-1。若仓库在点 (x,y) 处,现在将仓库移动到点 (x,y+1)同理计算即可。
#include<cstdio>
using namespace std;
#define min(a,b) (a<b ? a:b)
int a[110][110],s[110][110];
inline int calc(int A,int B,int C,int D) {
return s[C][D]-s[A-1][D]-s[C][B-1]+s[A-1][B-1];//计算左上角为(A,B),右下角为(C,D)的矩形中数的总和
}
int main() {
int T,n,m,now,nxt,ans;
scanf("%d",&T);
while(T--) {
now=nxt=0,ans=0x3f3f3f3f;
scanf("%d%d",&m,&n);
for (int i=1,x; i<=n; i++)
for (int j=1; j<=m; j++)
scanf("%d",&x),now+=(i-1+j-1)*x,//now计算开始位置(1,1)时到所有点的距离和
s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+x;//二维前缀和
for (int i=1; i<=n; i++) {
nxt=now;
for (int j=1; j<=m; j++) {
ans=min(ans,nxt);
nxt+=calc(1,1,n,j)-calc(1,j+1,n,m);// (x,y)移动到(x,y+1)
}
now+=calc(1,1,i,m)-calc(i+1,1,n,m);// (x,y)移动到(x+1,y)
}
printf("%d\n",ans);
}
}
中位数
这个有点稍微难理解,先放一个简单题目理解一下题目链接
这一题是给出每个地点的坐标,每个地方只需去一次,求将货仓建在哪个坐标上到各点距离和最小,答案中货仓的坐标就是所有坐标中的中位数(所有数与中位数的绝对差之和最小。)
那么,我们可以用货仓选址中的中位数来解决这题吗?货仓选址与这一题很相似,但也有不同,有什么不同呢?
1:每个地方可能去多次:
我们想到,其实没必要按照题目中的一个地方去多次,可以改成多个地方,每个地方只去一次,这样就可做了
2:二维坐标:
想想能不能由一维的情况拓展到二维呢?其实也是可以的,我们先把每一行都看做一个整体,变成一维,再用中位数的方法找到最优行(即仓库选在这一行到其他行之间的距离和最小,其实就是选在这一行的仓库到其他行中地点的竖直距离和最小);然后再把每一列都看做一个整体做一遍,得到最优列(即仓库选在这一列到其他列之间的距离和最小,也就是选在这一列的仓库到其他列中地点的水平距离和最小),那么最终仓库在哪最优呢?当然是最优行与最优列的交点!
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
int s[110][2],x[110],y[110],ans[2],sum;
inline void get_pos(int num,int lim) {//求中位数到底位于哪一行或那一列,当然你不用二分直接扫一遍s数组判断也可以
int l=1,r=lim;
while(l<=r) {
int mid=(l+r)/2;
if (s[mid][num]<sum/2) l=mid+1;//sum/2,中位数,也就是最优的位置
else r=mid-1,ans[num]=mid;//返回最优行最优列的位置
}
}
int main() {
int T,n,m,_ans;
scanf("%d",&T);
while(T--) {
scanf("%d%d",&m,&n),sum=_ans=0;
memset(x,0,sizeof x);
memset(y,0,sizeof y);
for (int i=1,a; i<=n; i++)
for (int j=1; j<=m; j++) {
scanf("%d",&a),sum+=a;//sum统计一共有多少个地方,实际上把一个地方去a次,改成了a个地方,每个地方只去一次
x[i]+=a,y[j]+=a;//x,y分别统计每行、每列的数字和,即把每行、每列看为一个整体
}
for (int i=1; i<=n; i++) s[i][0]=s[i-1][0]+x[i];
for (int i=1; i<=m; i++) s[i][1]=s[i-1][1]+y[i];//前缀和
get_pos(0,n),get_pos(1,m);//找最优行与最优列
for (int i=1; i<=n; i++) _ans+=abs(ans[0]-i)*x[i];//计算所有竖直距离
for (int i=1; i<=m; i++) _ans+=abs(ans[1]-i)*y[i];//计算所有水平距离
//当然这个计算答案用二维的(abs(x - i) + abs(y - j))也可以
printf("%d\n",_ans);
}
}
后记
感觉中位数这个操作不太常见,主要学习一下二维前缀和
参考链接https://blog.nowcoder.net/n/5cd4488286454cea86d9643159f12fa3