LA3983 Robotruck (单调队列优化DP)

本文探讨了一道关于机器人在不同限制下收集垃圾并返回原点的问题,通过动态规划及单调队列优化策略实现了高效的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Problem links: https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=1984

题目大意:给出一些垃圾点的坐标x,y(x,y为非负整数)以及每个垃圾的重量,给出机器人的携带量cap,垃圾桶在原点(0,0),机器人从原点出发按顺序拣垃圾,在满足不超过cap的情况下,把垃圾带回原点(回到原点后,携带量重新为cap)。问把所有垃圾拣完需要走的最短路。

一开始想到dp[i][res]表示当前到达i点,当前剩余携带量为res时的最优解。此时,决策有两种,一是把原先携带的先带回原点,即x[i-1]+y[i-1],然后再从原点直接出发到i。另一决策可以是,在满足剩余携带量不小于w[i]时,从i-1走到i。

即状态转移为dp[i][res] = min(x[i-1]+y[i-1]+x[i]+y[i]+dp[i+1][cap-w[i]], x[i]-x[i-1]+y[i]-y[i-1]+dp[i+1][res-w[i]]);复杂度为O(n*cap),其中n<=100000,cap<=100,复杂度很大。但是一开始看错了,以为n<=10000,直接写了个记忆化DP,居然过了。。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

#define SIZE 100010
int cas,cap,n;
int x[SIZE],y[SIZE],w[SIZE]; 
int dp[SIZE][110];
bool vis[SIZE][110];

int Abs(int x)
{
	return x>0?x:-x;
}
int dfs(int cur,int res)
{
	if(cur == n+1)
		return (x[n]+y[n]);
	if(vis[cur][res])
		return dp[cur][res];
	int tem1 = 0, tem2 = 0;
	tem2 += x[cur-1]+y[cur-1];
	tem2 += x[cur]+y[cur];
	tem2 += dfs(cur+1,cap-w[cur]);
	if(res >= w[cur])
	{
		tem1 += Abs(x[cur]-x[cur-1])+Abs(y[cur]-y[cur-1]);
		tem1 += dfs(cur+1,res-w[cur]);
		tem2 = min(tem1,tem2);
	}
	dp[cur][res] = tem2;
	vis[cur][res] = true;
	return dp[cur][res];
}
int main()
{
	scanf("%d",&cas);
	while(cas--)
	{
		scanf("%d%d",&cap,&n);
		for(int i=1; i<=n; i++)
			scanf("%d%d%d",&x[i],&y[i],&w[i]);
		memset(dp,0,sizeof(dp));
		memset(vis,0,sizeof(vis));
		dp[1][cap] = x[1]+y[1]+dfs(2,cap-w[1]);
		printf("%d\n",dp[1][cap]);
		if(cas)
			puts("");
	}
	return 0;
}

正解应该是用单调队列优化,及时删掉肯定不是最优解的点。复杂度降为O(n)

令dp[i]表示拣掉前i个垃圾,并送回原点的最小值。则状态转移方程为:

dp[i] = dp[k] + dis[k+1] + tot[i]-tot[k+1] + dis[i];其中dis[k+1]表示点k+1到原点的直接距离,方程中表示前k个点已经走完,并且机器人在原点,dis[k+1]既是从原点直接走到点k+1,tot[i]表示从点1沿着顺序一直走到点i,即1,2,3...i,故tot[i]-tot[k+1]表示从k+1走到i,最后,再从i走回原点,距离为dis[i]。但是直接枚举k肯定是不行的,这里就用到单调队列来优化。首先,最后结果肯定是dp[n],所以按顺序计算dp[1~n]时,前面肯定是有些子状态是会随着计算后变成没用了。按题意可以知道,当 w[k]+w[k+1]+...w[i] > cap的时候,k就是用不上的了,因为i不可能从k转移而来了。然后,假设有子状态dp[j],dp[k],转移时有dp[i]=dp[j]+dis[j+1]+tot[i]-tot[j+1]+dis[i],以及dp[i] = dp[k]+dis[k+1]+tot[i]-tot[k+1]+dis[i],若dp[i]从dp[j]中转移而来比从dp[k]中转移而来更优(即值更小),则有dp[j]+dis[j+1]+tot[i]-tot[j+1]+dis[i] < dp[k]+dis[k+1]+tot[i]-tot[k+1]+dis[i],化简得dp[j]+dis[j+1]-tot[j+1] < dp[k]+dis[k+1]-tot[k+1],所以可以将最优子子状态存到一个队列里,当当前i不能从队头转移而来时,删掉队头,当决策i比队尾更优时,删掉队尾的那个决策,因为后续状态肯定是从更优的决策i转移而来了。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

#define SIZE 100010
int cas,cap,n;
int x[SIZE],y[SIZE],w[SIZE];
int dp[SIZE],dis[SIZE],tot[SIZE],sumw[SIZE];
int q[SIZE],h,t;

int Abs(int x)
{
    return x>0?x:-x;
}

int main()
{
    scanf("%d",&cas);
    while(cas--)
    {
        scanf("%d%d",&cap,&n);
        x[0] = y[0] = sumw[0] = tot[0] = 0;
        for(int i=1; i<=n; i++)
        {
            scanf("%d%d%d",&x[i],&y[i],&w[i]);
            dis[i] = x[i] + y[i];
            tot[i] = tot[i-1] + Abs(x[i]-x[i-1]) + Abs(y[i]-y[i-1]);
            sumw[i] = sumw[i-1] + w[i];
        }
        dp[0] = 0;
        h = t = 0;
        q[++t] = 0;
        for(int i=1; i<=n; i++)
        {
            while(h+2 <= t && sumw[i]-sumw[q[h+1]] > cap)
                ++h;
            dp[i] = dp[q[h+1]] + dis[q[h+1]+1] + dis[i] + tot[i] - tot[q[h+1]+1];
            while(h+2 <= t && dp[i]+dis[i+1]-tot[i+1] <= dp[q[t]]+dis[q[t]+1]-tot[q[t]+1])
                --t;
            q[++t] = i;
        }
        printf("%d\n",dp[n]);
        if(cas)puts("");
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值