题意:有T组数据,每组数据有最大承重C和垃圾数n,接下来n行为第i个垃圾的坐标和重量,有一个机器人,从原点(0,0)出发,按照垃圾编号从小到大捡垃圾,且携带的垃圾总重量不能超过C,当捡的垃圾快要超过C时,机器人可以选择回到原点扔掉,问机器人捡完所有垃圾行走的最短总路程(两点的距离为曼哈顿距离,即|xi-xj|+|yi-yj|)
思路:设d[i]为捡完前i个垃圾行走的最短距离,dist2origin[i]为第i个垃圾到原点距离,dist(i,j)为垃圾i到i+1,i+2,..垃圾j的总距离,很容易想到,d[i]=min{d[j]+dist2origin[j+1]+dist(j+1,i)+dist2origin[i]}(w(j+1,i)<=C),但是这个方程不好求解,新的状态总是与久的不一样,可以转换一下,设total_dist[i]为垃圾1,2,3,..i的总距离,于是dist(j+1,i)=total_dist[i]-total_dist[j+1],只要维护min{d[j]+dist2origin[j+1]-total_dist[j+1]}就可以了,怎么维护,可用线段树维护(因为j的范围一直在变,动态维护区间最小值用线段树),但是刘汝佳在白书中给出了一种更牛逼的操作,队列维护,每次加入一个新的元素,就把该区间内所有比新元素大的值全部删除,造成队列里的元素单调递增,代码如下。
#include<cstdio> #include<algorithm> using namespace std; const int maxn=100000+10; int x[maxn],y[maxn]; int total_dist[maxn],total_weight[maxn],dist2origin[maxn]; int q[maxn],d[maxn]; int func(int i) { return d[i]-total_dist[i+1]+dist2origin[i+1]; } int main() { int T,c,n,w,front,rear; scanf("%d",&T); while(T--) { scanf("%d%d",&c,&n); total_dist[0]=total_weight[0]=x[0]=y[0]=0; for(int i=1;i<=n;i++) { scanf("%d%d%d",&x[i],&y[i],&w); dist2origin[i]=abs(x[i])+abs(y[i]); total_dist[i]=total_dist[i-1]+abs(x[i]-x[i-1])+abs(y[i]-y[i-1]); total_weight[i]=total_weight[i-1]+w; } front=rear=1; for(int i=1;i<=n;i++) { while(front<=rear&&total_weight[i]-total_weight[q[front]]>c) front++; d[i]=func(q[front])+total_dist[i]+dist2origin[i]; while(front<=rear&&func(i)<=func(q[rear])) rear--; q[++rear]=i; } printf("%d\n",d[n]); if(T>0) printf("\n"); } }