题意:在平面上有N个垃圾,告诉每个垃圾的坐标。现在有一个机器人从原点出发,按照给定的顺序把垃圾捡回来。同时每个垃圾都有重量,机器人有最大装载重量C。垃圾之间和垃圾和原点之间的距离定义为两点坐标的曼哈顿距离。现在求最少的距离,使机器人完成这件任务。
思路:看到N=100000,就该去想O(N)的算法了。这里我们用DP来求解。
设dp[i]表示捡完前i个垃圾,并回到原点的最少距离。
我们可以得到递推式:dp[i] = min(dp[k] - dis[k] + ds[i] - ds[k] + dis[i]), ws[i] - ws[k] <= C,其中dis[i]表示第i个垃圾到原点的距离,ds[i]表示前i个垃圾的距离的和,ws[i]表示前i个垃圾的总重量。
我们可以看到,dis[i],ds[i]是和k无关的项,变形得:dp[i] = min(dp[k] - dis[k] - ds[k]) + ds[i] + dis[i] ,ws[i] -ws[k] <= C.这就转化成了求前面一项的最小值。再次,我们发现,前面一项也只是和k有关,而对于固定的k,这也是个定值。这样,我们就能用单调队列优化,在队列的头保存最小值。
代码如下:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 100100;
int x[MAXN], y[MAXN], w[MAXN];
int dist[MAXN], sumw[MAXN], dis[MAXN];
int q[MAXN], dp[MAXN];
int func(int i)
{
return dp[i] - dist[i + 1] + dis[i + 1];
}
int main()
{
//freopen("input.txt", "r", stdin);
int T;
int C, N, head, tail;
scanf("%d", &T);
while (T--)
{
scanf("%d %d", &C, &N);
dist[0] = dis[0] = 0;
for (int i = 1; i <= N; i++)
{
scanf("%d %d %d", &x[i], &y[i], &w[i]);
dist[i] = dist[i - 1] + abs(x[i] - x[i - 1]) + abs(y[i] - y[i - 1]);
dis[i] = abs(x[i]) + abs(y[i]);
sumw[i] = sumw[i - 1] + w[i];
}
head = tail = 0;
q[tail++] = 0;
for (int i = 1; i <= N; i++)
{
while (head + 1 < tail && sumw[i] - sumw[q[head]] > C)
head++;
dp[i] = func(q[head]) + dis[i] + dist[i];
while (head + 1 < tail && func(i) <= func(q[tail-1]))
tail--;
q[tail++] = i;
}
//for (int i = 1; i <= N; ++i)
printf("%d\n", dp[N]);
if (T)
puts("");
}
return 0;
}