本渣也是在努力地刷着大白,虽然基本每题都做不出,但是每题都能理解,然后自己实现,再写个报告,希望这样的方法能有用。
这题给出了n个垃圾的坐标和重量,然后机器人的最大承重是c,捡垃圾的时候必须按照垃圾的顺序捡。
这题一上来我就想到背包,要么捡,要么回去,这样的方法其实是可以的,只是复杂度比较高,如果数据比较坑,可能会过不了。但是这题可以。
大白上介绍的方法是一种非常巧妙的单调队列优化。
origin数组是原点到点i的曼哈顿距离,total数组是沿着一个个垃圾的顺序,到垃圾i的距离。
d[i]表示收集了垃圾i之后回到原点所需要的最短距离,所以答案就是d[n],d[i]=d[j]+origin[j+1]+(total[i]-total[j+1])+origin[i];满足第j+1个垃圾到第i个垃圾的重量和小于等于c
状态转移方程:d[i]=min(d[j]+origin[j+1]-total[j+1])+origin[i]+total[i]。
单调队列优化就是维护一段区间,这段区间内从头到尾的垃圾重量都小于等于c,并且距离单调递增,考察一个新的垃圾i,如果这个垃圾到队列首元素的重量和大于c,那么队首元素就要舍弃,因为大于c之后就不能从队首元素开始运,弹出第一个元素,继续考察队首元素,直到队列的重量和小于等于c。d[i]=从此时的队首元素开始到i的距离(因为这是单调队列,队列里的其他元素d[k]都大于d[front],所以垃圾i必定是从队首元素开始运的)
然后如果当前计算出来的d[i]小于等于队列最后的元素,就要舍弃比d[i]大或者相等的元素,如果比d[i]大,那么运后面的垃圾,显然从i开始比从前面几个垃圾开始更优。
如果相等,那么从i开始不会比从前面几个开始更坏,因为i到后面某个元素的重量和更小,如果在i前面的垃圾,d的值和i的一样,也许那个垃圾到后面某个垃圾的重量和会超出c,就得不到最优解了。
所以就是维护区间的重量和小于等于c,如果不满足,头指针++,如果得到的解比队列最后的元素更小,那么就插到比它小的元素后面,并且比它大的舍弃。
#include<iostream> #include<cstdio> #include<cctype> #include<cstdlib> #include<cmath> #include<algorithm> #include<cstring> #include<string> #include<vector> #include<queue> #include<map> #include<set> #include<sstream> #include<stack> using namespace std; #define MAX 100005 typedef long long LL; const double pi=3.141592653589793; const int INF=1e9; const double inf=1e20; const double eps=1e-6; int x[MAX],y[MAX]; int total[MAX];//记录按顺序从1-i走的距离 int origin[MAX];//记录从原点走到i的距离 int w[MAX];//记录前i个垃圾的重量 int c,n; int d[MAX];//记录收集了第i个垃圾后运回去后所走的最短距离 int q[MAX];//滑动窗口单调队列,优化队列 int func(int i){ return d[i]+origin[i+1]-total[i+1]; } int main(){ int t,p; cin>>t; while(t--){ scanf("%d %d",&c,&n); x[0]=y[0]=w[0]=total[0]=origin[0]=0; for(int i=1;i<=n;i++){ scanf("%d%d%d",&x[i],&y[i],&p); w[i]=w[i-1]+p; total[i]=total[i-1]+fabs(x[i]-x[i-1])+fabs(y[i]-y[i-1]); origin[i]=fabs(x[i])+fabs(y[i]); } int front=0,last=0; q[front]=0; for(int i=1;i<=n;i++){ while(front<=last&&w[i]-w[q[front]]>c) front++; d[i]=func(q[front])+total[i]+origin[i]; while(front<=last&&func(i)<=func(q[last])) last--; q[++last]=i; } printf("%d\n",d[n]); if(t>0) printf("\n"); } return 0; }