题目链接
解题思路
我们可以将这题简化的看成这样一个问题:有一个x坐标轴,你现在位于原点0,在你的两旁,分别有若干堆信件,这些信件的位置为
x
i
x_i
xi (
x
i
x_i
xi 为正,则信件在坐标轴的正向;为负,则在坐标轴的反方向),每堆信件都有一定的数量
t
i
t_i
ti。
你要做的就是从原点出发,把所有的信件都搬到原点(题目是送信,不过都一样啦,都是要满足这些点的需求),当然,你一次最多只能搬
k
k
k 封信件,问搬完所有的信件走的走的最少路程。
对于原点两边的信件,我们的选择肯定是分开处理,就是说搬x轴正向的信件时不会经过x轴反方向。所以,我们把输入的数据分别存在两个数组里,分别处理这两个数组,当然处理方法是一样的。
存储数据用到结构体:
struct node{
//a 距离
//b 信封数量
int a, b;
};
数组定义如下:
//f 左边的信封
//z 右边的信封
node f[1050], z[1050];
我们以x轴正向为例。假设有 zn
堆信封,用 z[]
存右边信封的数据,我们来想一下,在最远一堆信封前面的信封都有可能在运后面的信封的过程中顺带带走,但是最后一堆信封不能这样,如果 z[zn-1].b
(最后一堆信封的数量) 不是
k
k
k 的倍数,那么我们在运完 z[zn-1].b / k
趟后,还要再跑一趟把剩余的信封运走,这时如果只运走这些剩余的信封,我们还有余力运更多的信封,那么我们为何不好好利用这一趟呢?与其只运走剩余的的信封,不如沿途中顺带带走其它堆的信封,那么如何是最优的呢?最优的情况是:运走距离最后一堆最近的几堆信封。这样从后往前不断地贪心即可解决问题。
x轴负方向和正向一样。
源代码如下:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
struct node{
int a, b;
};
bool cmp(node a, node b) {
return a.a < b.a;
}
//贪心求解
ll solve(node fz[], int k, int n) {
ll res = 0;
for (int i = n-1; i >= 0; --i) {
//fz[i].b%k 搬第i堆最后一趟剩余的信封
//diff 需要从前面的信封堆中减去的信封数量
int diff = k-fz[i].b%k;
if (diff == k) {
res += fz[i].b / k * fz[i].a;
}
else {
res += (fz[i].b / k + 1) * fz[i].a;
//从前面的信封堆中减去diff个信封
for (int j = i-1; j >= 0 && diff; --j) {
if (fz[j].b <= diff) {
diff -= fz[j].b;
fz[j].b = 0;
}
else {
fz[j].b -= diff;
diff = 0;
}
}
}
}
return res;
}
int main(int argc, char** argv) {
//f 左边的信封
//z 右边的信封
node f[1050], z[1050];
int n, k;
//fn x轴负方向信封数量
//zn x轴正向信封数量
int fn = 0, zn = 0;
cin >> n >> k;
int a, b;
for (int i = 0; i < n; ++i) {
cin >> a >> b;
if (a < 0) {
//把负的数转换成正数
f[fn].a = -a;
f[fn++].b = b;
}
else {
z[zn].a = a;
z[zn++].b = b;
}
}
f[fn].b = 0;
z[zn].b = 0;
//因为要从后往前贪心,因此排序是必要的
sort(f, f+fn, cmp);
sort(z, z+zn, cmp);
//左右分开处理
ll res = solve(f, k, fn);
res += solve(z, k, zn);
//因为计算的是去的路程,回来的路程没算,所以要乘2
printf("%lld\n", res*2);
return 0;
}