UVA1169 Robotruck(DP + 单调队列)

UVA1169 Robotruck(DP + 单调队列)

思路很简单,并且要求捡垃圾的顺序是按照垃圾的编号从小到大进行收集,所以也降低了难度,我们很容易想到用一个双重循环的区间DP来做,可惜提交之后发现超时了,在这里我们用到的是滑动窗口单调队列的做法进行优化。
我们设动态数组dp[i]为收集前 i 个垃圾所需要的的曼哈顿距离,(之后我们用距离来代表曼哈顿距离)所以我们的动态转移方程是:
dp[i] = min(dp[i], dp[j] + 从原点到第 j+1 号垃圾的距离 + 从第 j+1 号垃圾到第 i 号垃圾的总距离 + 从第 i 号垃圾到原点的距离)
因为只有从 j+1 号垃圾到第 i 号垃圾的总重量不超过最大负载,所以只有满足这个条件的 j 才能进行 i 值的更新。
我们令 dis(i,j) 表示从 i 号到 j 号的距离(因为是从原点开始的,所以我们把(0,0)记录为0号垃圾);
我们令 dist[i] 表示从原点依次经过1号,2号,三号…… i号垃圾的总距离,所以dist[i] = dist[i-1] + dis(i-1,i);
我们令 sum[i] 定义为前 i 个垃圾总共的重量,
sum[i] = sum[i-1] + weight[i];
之后我们就可以得到最开始的动态转移方程了:

	for(int i=1;i<=n;i++){
		for(int j=1;j<i;j++){
			if(sum[i] - sum[j]<=m){
				//从原点到第j+1个垃圾,再从第j+1个垃圾一直到到第i个垃圾,再回到原点
				dp[i] = min(dp[i], dp[j] + dis(0,j+1) + dist[i] - dist[j+1] + dis(i,0);
			}
		}
	}

但是这样肯定会超时的,所以我们就想办法进行优化,观察动态转移方程:
dp[i] = min(dp[i], dp[j] + dis(0,j+1) + dist[i] - dist[j+1] + dis(i,0);
我们发现当 i 固定的时候,改变的就只是:
dist[j] + dis(0,j+1) - dist[j+1];
所以我们定义函数ass(int j) : return dist[j] + dis(0,j+1) - dist[j+1];
在遍历的过程中不断维护符合条件的最大值即可,假如我们有一个队列,里面的元素都是从大到小进行排列的,咋一听有点类似与priority_queue,但是我们在加入队列内元素的时候如下进行操作:

加入原先队列中有6 2两个元素,依次加入4 3 7 3 3 2:
加入4:6 4
加入3:6 4 3
加入7:7
加入3: 7 3
加入3: 7 3 3
加入2: 7 3 3 2
大家大概可以看出这个单调队列的实现过程了吧,队列的长度是len,也就是说有len个元素,末尾元素是queue[len],加入一个数 i ,
while(len >0 && i >queue[len])len–;
queue[++len] = i;
这样就实现了一个入队的过程,但是我们要把这个实际应用到这道题中,所以单纯的这样写是不可以的,我们继续分析:

在这里我们并不是队列中的元素进去了就不会再出来了,是随着i 的更新进行一点点的更新的,因为如果入队一个i,那么队头的那个元素就可能不再适合这个i了,因为一直有一个约束条件:sum[i] - sum[j] >= m,在这里也就是:
sum[i] - sum[queue[front]] >= c
所以我们设置两个变量,一个是front记录队头的位置,一个tag记录队尾的位置:

for(int i=1;i<=n;i++){
	//找到符合条件的最大的 j ,这里队列中存的都是垃圾的编号
	while(front<=tag && sum[i]-sum[queue[front]]<=m) front++;
	//找到了编号了就进行值的更新
	dp[i] = min(dp[i],ass(queue[front] + dist[i] + dis(i,0));
	//第 i 个元素入队,跟上面举的例子差不多
	while(front<=tag && queue[tag] < weight[i]) tag--;
	queue[++tag] = i;
}

经过单调队列的优化就可以过了,还有大佬用线段树来进行优化,后续再补这个吧(狗头保命)
完整代码如下:

#include<iostream>
#include<cmath>
#include<cstring>
using namespace std;
const int maxn = 1e6+10;
int m,n;
int queue[maxn];
struct infor{
	int x,y;
	int cost;
}map[maxn];
int dp[maxn];
int sum[maxn];
int dist[maxn];
int dis(int a,int b){
	return abs(map[b].x - map[a].x) + abs(map[b].y - map[a].y);
}
int ass(int j){
	return dp[j] + dis(0,j+1) - dist[j+1];
}
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		int front,tag;
		scanf("%d%d",&m,&n);
		memset(dp,0x3f,sizeof(dp));
		dp[0] = sum[0] = dist[0] = map[0].x = map[0].y = map[0].cost = 0;
		
		for(int i=1;i<=n;i++){
			scanf("%d%d%d",&map[i].x,&map[i].y,&map[i].cost);
			sum[i] = sum[i-1] + map[i].cost;
			dist[i] = dist[i-1] + dis(i-1,i);
		}
		//ass(j) + dist[i] + dis(0,i)
		front = tag = 1;
		queue[1] = 0;
		for(int i=1;i<=n;i++){
			while(front<=tag && (sum[i] - sum[queue[front]]>m)) front++;
			dp[i] = ass(queue[front]) + dist[i] + dis(0,i);
			while(front <= tag && ass(i) <= ass(queue[tag])) tag--;
			queue[++tag] = i;
		}
		printf("%d\n",dp[n]);
		if(t!=0) cout << endl;
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值