UVA3983 Robotruck 题解
题目描述
有n个垃圾,第i个垃圾的坐标为(xi,yi),重量为wi。有一个机器人,要按照编号从小到大的顺序捡起所有垃圾并扔进垃圾桶(垃圾桶在原点(0,0))。机器人可以捡起几个垃圾以后一起扔掉,但任何时候其手中的垃圾总重量不能超过最大载重C。两点间的行走距离为曼哈顿距离(即横坐标之差的绝对值加上纵坐标之差的绝对值)。求出机器人行走的最短总路程(一开始,机器人在(0,0)处)。
输入格式:
输入的第一行为数据组数T(1<=T<=15),每组数据的第一行为最大承重C(1<=C<=100);第二行为正整数n(1<=n<=100000),即垃圾的数量;一下n行每行为三个非负整数x,y,w,即坐标和重量(重量保证不超过C)。
输出格式:
对于每组数据,输出总路径的总长度。
又是一道单调队列优化的DP。按照这种题目的一般解决过程,我们应当先写出暴力DP的状态转移方程,然后再针对其方程的特点选择合适的优化方式进行优化。
先前缀和预处理一下吧,有什么用往下看就知道了。我们用s[i]表示1--i个垃圾的总重量,step[i]表示1--i的总路程长度(不回垃圾桶处)。
然后,我们设f[i]表示捡了第i个垃圾并回到垃圾桶的最短的总路程的长度,那么对于每一个i,我们枚举j(1<=j<=i&&s[i]-s[j-1]<=C),意为“捡完1--j-1的全部垃圾回到垃圾桶后,再出发一次性捡完j--i的所有垃圾”,其中s[i]-s[j-1]<=C是因为机器人所捡垃圾重量不能超过他的最大载重。
那么状态转移方程是什么呢?显然,捡完1--j-1的全部垃圾并回到垃圾桶处后,机器人必须先得到达j点,然后一步一步走到i点,最后再走回原点,分为三段,所以状态转移方程就显而易见了:
\[f[i]=min(f[j-1]+x[j]+y[j]+step[i]-step[j]+x[i]+y[i])(1<=j<=i且s[i]-s[j-1]<=C)
\]
由于i在当下是固定的,所以(step[i]+x[i]+y[i])的值也是固定的,那么想要所取的值最小,就是要(f[j-1]+x[j]+y[j]-step[j])的值最小。对于维护最小值的事,当然要请来单调队列啦,我们设x=f[j-1]+x[j]+y[j]-step[j],将x与单调队列的队尾元素的值进行比较,取较小的那一个。同时,队首j也要及时判断是否满足(s[i]-s[j-1]<=C),不满足即出队。
代码如下:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
int T,C,n,x[100001],y[100001],w[100001];
long long s[100001],step[100001],f[100001],q[100001],p[100001],head,tail,k;
int main()
{
scanf("%d",&T);
for(register int i=1;i<=T;i+=1)
{
scanf("%d",&C);
scanf("%d",&n);
for(register int i=1;i<=n;i+=1)
{
scanf("%d%d%d",&x[i],&y[i],&w[i]);
s[i]=s[i-1]+w[i];
step[i]=step[i-1]+abs(x[i]-x[i-1])+abs(y[i]-y[i-1]);
}
head=1;tail=0;
for(register int i=1;i<=n;i+=1)
{
k=f[i-1]+x[i]+y[i]-step[i];
while(head<=tail&&p[tail]>=k)tail--;
q[++tail]=i;
p[tail]=k;
while(head<=tail&&s[i]-s[q[head]-1]>C)head++;
f[i]=p[head]+step[i]+x[i]+y[i];
}
printf("%lld\n",f[n]);
if(i!=T)printf("\n");
}
return 0;
}