这是一道思维层层递进的题。
注意到N最大是100000,C最大是100,所以我们不能设时间复杂度为O(NC)的状态。
只能设O(N)的状态了。
设d(i)为从原点出发,将前i个垃圾全部扫完又回到原点的最小代价。(注意这里设的是从原点出发又回到原点,这是为了状态转移的方便而设)
经典的想法还是把前i个垃圾进行分割。
d(i) = min{d(j) + dist2origin[j + 1] + dist[j + 1,i] + dist2origin[i] | j <= i, w(j+1,i) <= C}
搞完前j个的代价 + 从原点移动到j+1的代价 + 从j+1扫到i的代价 + 从i回到原点的代价。条件是在拿着j+1到i这一堆垃圾的时候总总量小于等于C。
显然直接枚举是一个O(N2)的东西,我们想到dp加速。
怎么加速,把与j有关的东西抽取出来,每次取当前可以选的j中的最小值。(因为与i有关的值可以O(1)求出)
这就是利用了单调队列!
把j相关的量分离出来有:
d(i) = min(d(j) - total_dist[j+1]+dist2origin(j+1)) + total_dist[i] + dist2origin[i](j同样要满足上面的限制条件)
如果令func(j) = d(j) - total_dist[j + 1] + dist2origin[j+1]
则d(i) = min(func(j)) + total_dist[i] + dist2origin[i]
这个用单调队列搞就好了。
#include <cstdio> #include <algorithm> using namespace std; const int maxn = 100005; int t, C, n; int dist2origin[maxn], total_dist[maxn], total_weight[maxn], f[maxn], q[maxn]; struct node { int x, y, w; }a[maxn]; int func(int i) { return f[i] - total_dist[i + 1] + dist2origin[i + 1]; } void solve() { scanf("%d", &C); scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%d%d%d", &a[i].x, &a[i].y, &a[i].w); dist2origin[i] = abs(a[i].x) + abs(a[i].y); total_dist[i] = total_dist[i - 1] + abs(a[i].x - a[i - 1].x) + abs(a[i].y - a[i - 1].y); total_weight[i] = total_weight[i - 1] + a[i].w; } int front = 1, rear = 1; for (int i = 1; i <= n; i++) { while (front <= rear && total_weight[i] - total_weight[q[front]] > C)//把已经不合适的队头元素清掉。 front++; f[i] = func(q[front]) + total_dist[i] + dist2origin[i];//这样取出来的队头就是func值最小的元素。 while (front <= rear && func(i) <= func(q[rear]))//保证队列是单调递增的。 rear--; q[++rear] = i;//不要忘了 } printf("%d ", f[n]); if (t > 0) printf(" "); } int main() { scanf("%d", &t); while (t--) solve(); return 0; }
时间复杂度O(N)