LA 3983 Robotruck / 单调队列

时间:2020-12-17 11:12:15

n个垃圾 机器人从(0,0)开始要按照次序捡垃圾 再回到原点 可以带回多个并且重量之和不能大于C

思路和代码全部参考书上的 求走过最小的距离

两点的距离为abs(x1-x2)+abs(y1-y2)

dp(i)为捡完第i个垃圾 并且回到原点的最小距离

dis(i)从原点到第i个垃圾的记录

dist(i)为从第1个 经过第2个 第三个到第i个垃圾的距离 那么从第j个到第i个垃圾的距离为 dist(i)-dis(j);

w(i,j)表示第i个到第j个垃圾重量之和

dp(i) = dp(j) +dis(j+1)+dist(i)-dist(j+1)+dis(i) = dp(j)-dist(j+1)+dis(j+1)+dis(i)+dist(i)  并且w(j+1,i) <= C

这样 把有关于i的都提出

只需考虑j 求出最小的 dp(j)-dist(j+1)+dis(j+1) 如果枚举j 会超时

现在求的是一个j < i满足 dp(j)-dist(j+1)+dis(j+1) 尽量小 并且w(j+1,c) <= C

可以用一个单调队列维护最小值

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int maxn = 100010;
int x[maxn], y[maxn], w[maxn];
int dist[maxn], weight[maxn], dis[maxn];
int q[maxn], dp[maxn];

int func(int i)
{
	return dp[i] - dist[i+1] + dis[i+1];
}
int main()
{
	int T;
	int C, n, front, rear;
	scanf("%d", &T);
	while(T--)
	{
		scanf("%d %d", &C, &n);
		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]);
			weight[i] = weight[i-1] + w[i];
		}
		front = rear = 1;
		for(int i = 1; i <= n; i++)
		{
			while(front <= rear && weight[i] - weight[q[front]] > C)
				front++;
			dp[i] = func(q[front]) + dis[i] + dist[i];
			while(front <= rear && func(i) <= func(q[rear]))
				rear--;
			q[++rear] = i;
		}
		printf("%d\n", dp[n]);
		if(T)
			puts("");
	}
	return 0;
}