LA3983 Robotruck (单调队列优化DP)

来源:互联网 发布:js求二维数组的最大值 编辑:程序博客网 时间:2024/04/30 15:41

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 100010int 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 100010int 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("");    }}


原创粉丝点击