本文出自 http://blog.csdn.net/shuangde800
题目: 点击打开链接
题目大意
(LRJ《训练指南》)
有n个垃圾,第i个垃圾的坐标为(xi,yi),重量为wi。有一个机器人,要按照编号从小到大的顺序捡起所有垃圾并扔进垃圾桶(垃圾桶在原点(0,0))。机器人可以捡起几个垃圾以后一起扔掉,但任何时候其手中的垃圾总重量不能超过最大载重C。两点间的行走距离为曼哈顿距离(即横坐标之差的绝对值加上纵坐标之差的绝对值)。求出机器人行走的最短总路程(一开始,机器人在(0,0)处)。
【输入格式】
输入的第一行为数据组数。每组数据的第一行为最大承重C(1≤C≤100);第二行为正整数n(1≤n≤100 000),即垃圾的数量;以下n行每行为两个非负整数x, y和一个正整数w,即坐标和重量(重量保证不超过C)。
【输出格式】
对于每组数据,输出总路径的最短长度。
思路
方法一:
sumWeight[i], 表示前i个垃圾的总重量
假设要捡(j, i)这区间内的垃圾,
令w(j,i) = sumWeight[i] - sumWeight[j-1]; 表示(j,i)区间垃圾的总重量
机器人的走路路径为:0->j->j+1->..->i->0,这段路的总距离为:
routeDist(j, i) = getDis(0, j) + sumDis[i] - sumDis[j] + getDis(0, i)
设f(i)表示捡完前i个垃圾走的最短距离,那么可得到状态转移
f(i) = min{ f(j-1) + routeDist(j, i), 1<=j<=i && sumWeight[i]-sumWeight[j-1] >= C }
代码一
/**==========================================
* This is a solution for ACM/ICPC problem
*
* @source:uva-1169 Robotruck
* @type: dp
* @author: shuangde
* @blog: blog.csdn.net/shuangde800
* @email: zengshuangde@gmail.com
*===========================================*/
#include
#include
#include
#include
#include
#include
#include
using namespace std; typedef long long int64; const int INF = 0x3f3f3f3f; const int MAXN = 100010; int C, n; int sumDis[MAXN], sumWeight[MAXN]; int f[MAXN]; struct Node{ int x, y, w; }A[MAXN]; int getDis(int i, int j) { return abs(A[i].x-A[j].x) + abs(A[i].y-A[j].y); } int main(){ int nCase; scanf("%d", &nCase); while(nCase--) { scanf("%d%d", &C, &n); for(int i = 1; i <= n; ++i) { scanf("%d%d%d", &A[i].x, &A[i].y, &A[i].w); sumDis[i] = sumDis[i-1] + getDis(i, i-1); sumWeight[i] = sumWeight[i-1] + A[i].w; } for(int i = 1; i <= n; ++i) { f[i] = INF; for(int j = i; j >= 1; --j) { int w = sumWeight[i] - sumWeight[j-1]; if(w > C) break; int dis = getDis(0, j) + sumDis[i] - sumDis[j] + getDis(0, i); f[i] = min(f[i], f[j-1]+dis); } } printf("%d\n", f[n]); if(nCase) putchar('\n'); } return 0; }
方法二:优先队列优化
上面方程式的完整表达为:
f(i) = min{ f(j-1) + getDis(0, j) + sumDis[i] - sumDis[j] + getDis(i, 0) | 1<=j<=i && sumWeight[i]-sumWeight[j-1] >= C }
通过移项,可以转化为:
f(i) = min{ f(j-1) + getDis(0, j) - sumDis[j] | 1<=j<=i && sumWeight[i]-sumWeight[j-1] >= C } + sumDis[i] + getDis(i, 0)
为了让表达式更简介易懂,令
另func(j) = f(j-1) + getDis(0,j) - sumDis[j]) .
然后方程式为:
f(i) = min{ func(j) | 1<=j<=i && w(j,i)<=C} + sumDis[i] - getDis(0, i);
要让f(i)最小,只需要让func(j)最小即可。
所以我们可以维护一个j,使func(j)最小
这就是用到了单调队列来优化了。
单调队列方法的代码:
/**==========================================
* This is a solution for ACM/ICPC problem
*
* @source:uva-1169 Robotruck
* @type: 单调队列优化dp
* @author: shuangde
* @blog: blog.csdn.net/shuangde800
* @email: zengshuangde@gmail.com
*===========================================*/
#include
#include
#include
#include
#include
#include
#include
using namespace std; typedef long long int64; const int INF = 0x3f3f3f3f; const int MAXN = 100010; int C, n; int sumDis[MAXN], sumWeight[MAXN]; int f[MAXN]; struct Node{ int x, y, w; }A[MAXN]; int getDis(int i, int j) { return abs(A[i].x-A[j].x) + abs(A[i].y-A[j].y); } // func(j) = f(j-1)+getDis(0,j)-sumDis[j]) inline int func(int j){ return f[j-1] + getDis(0,j) - sumDis[j]; } // w(j,i) = sumWeight[i] - sumWeight[j-1] inline int w(int j, int i) { return sumWeight[i] - sumWeight[j-1]; } int main(){ int nCase; scanf("%d", &nCase); while(nCase--) { scanf("%d%d", &C, &n); for(int i = 1; i <= n; ++i) { scanf("%d%d%d", &A[i].x, &A[i].y, &A[i].w); sumDis[i] = sumDis[i-1] + getDis(i, i-1); sumWeight[i] = sumWeight[i-1] + A[i].w; } deque
que; for(int i = 1; i <= n; ++i) { while(!que.empty() && func(que.back()) >= func(i)) que.pop_back(); que.push_back(i); while(!que.empty() && w(que.front(), i) > C) que.pop_front(); f[i] = func(que.front()) + sumDis[i] + getDis(0, i); } printf("%d\n", f[n]); if(nCase) putchar('\n'); } return 0; }