n个垃圾,第i个垃圾坐标为(xi,yi),重量wi,有一个机器人,按照编号从小到大捡垃圾并扔进垃圾箱(垃圾桶在原点(0,0)),可以捡起几个垃圾后一起扔掉,但机器人持有垃圾总重量不得超过lim,两点间行走距离为曼哈顿距离(即横坐标之差加上纵坐标之差),求机器人行走最短路程
n<=1e5,c<=100
我们一开始能够很容易想到用当前垃圾序号和载重量来表示一个状态进行DP,当数据量过大,这样不可行(好吧,其实是因为有多组数据,不然 107 是可以过的),考虑f[i]表示前i个垃圾放回垃圾桶的最小距离,那么方程还是不难得出的
f[i]=min{f[j]+orgin[j+1]+dis[j+1,i]+orgin[i] |j小于i,w(j+1,i<=lim},其中orgin表示走到原点距离,ds,表示从j+1顺序走到i的距离和
然而要枚举j,这样时间复杂度还是要挂,考虑优化
首先将dis用前缀和表示出来,再令cal(j)=(f[j]-sum[j+1]+orgin[j),那么原方程又可以简化为,f[i]=min{cal(j)}+sum(i)+orgin(i),那么起作用的就只有cal了
我们把所有满足w(j+1,i)<=lim的j看做一个区间,显然随着i增大,这个区间会向右移动,那么其实这里就可以用优先队列+set来维护实现任意删除了,不过我不会,达到nlogn
还可以继续优化,就是用单调队列了
在区间中若是左边元素优先级比右边元素低,那么就意味着左边元素永远不会被用,所以应该及时将他删除,这就是单调队列的主要思想,具体实现请参见code
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1e5+5;
int T,lim,n,front,rear,w;
int orgin[maxn],x[maxn],y[maxn],sumw[maxn],sumdis[maxn],f[maxn],que[maxn];
int cal(int i)
{
return f[i]-sumdis[i+1]+orgin[i+1];
}
int main()
{
freopen("robert.in","r",stdin);
scanf("%d",&T);
while (T--)
{
scanf("%d%d",&lim,&n);
for (int i=1;i<=n;i++)
{
scanf("%d%d%d",&x[i],&y[i],&w);
orgin[i]=abs(x[i])+abs(y[i]);
sumdis[i]=sumdis[i-1]+abs(x[i]-x[i-1])+abs(y[i]-y[i-1]);
sumw[i]=sumw[i-1]+w;
}
front=rear=1; que[1]=0;
for (int i=1;i<=n;i++)
{
while (front<=rear&&sumw[i]-sumw[que[front]]>lim) front++;
f[i]=cal(que[front])+sumdis[i]+orgin[i];
while (front<=rear&&cal(i)<=cal(que[rear])) rear--;
que[++rear]=i;
}
printf("%d",f[n]);
if (T) putchar('\n');
}
return 0;
}