现在的位置: 首页 > 综合 > 正文

UVALive 3983 Robotruck (单调队列)

2018年02月22日 ⁄ 综合 ⁄ 共 1786字 ⁄ 字号 评论关闭

题目链接~~>

做题感悟:这是接触的单调队列的第一题,终于把它弄懂了,结合白书以及网上的各种资料。

解题思路:   单调队列DP

                这题的重点就在变换动态转移方程上,首先设 dp[ i ] 为处理到第 i 个垃圾所用的最小距离,那么很容易想到动态转移方程:dp[ i ] = min { dp[ j ] + d( j + 1 )  +  d( j + 1 , i ) + d( i ) }  且 w( j + 1 , i )  <= W , 解释一下:d( i )  为 i 点到原点的距离(指哈密顿距离),d( j + 1 ,i) 代表 从j + 1 到 i 的距离和。w(j + 1 , i ) 为从 j
+ 1 到 i 的重量和。W 为最大重量。

               好了,接下来就是怎样去转化动态方程了:我们用前缀和的思想分别记录前缀距离和前缀重量,分别用数组 sumd[ i ] ,sumw[ i ] ,d[ i ] 为点i 到原点的距离,这样方程就可以变为:

                dp[ i ] = min { dp[ j ]  + d[ j +1 ]  + sumd[ i ]  - sumd[ j + 1] + d[ i ]  } 且 sumw[ i ] - sum[ j ] <= W ;

进一步转变  ===> 

                    dp[ i ] = min{dp[ j ] + d[ j + 1 ]  - sumd[ j + 1 ] }  + sumd[ i ] + d[ i ]  ; 变成这样后你会发现前面的各个数只与本身相关,可以把他们放入一个队列中,只要维护队列的最小值(在重量满足的前提下),那么计算dp[ i ]的时间就变成O( 1 ) 的了。切记:队尾的结尾的元素为 end - 1 

具体见代码:

#include<iostream>
#include<sstream>
#include<map>
#include<cmath>
#include<fstream>
#include<queue>
#include<vector>
#include<sstream>
#include<cstring>
#include<cstdio>
#include<stack>
#include<bitset>
#include<ctime>
#include<string>
#include<cctype>
#include<iomanip>
#include<algorithm>
using namespace std  ;
#define INT long long int
#define L(x)  (x * 2)
#define R(x)  (x * 2 + 1)
const int INF = 0x3f3f3f3f ;
const double esp = 0.0000000001 ;
const double PI = acos(-1.0) ;
const int mod = 1000000007 ;
const int MY = (1<<5) + 5 ;
const int MX = 100010 + 5 ;
int n ,W ;
int sumw[MX] ,d[MX] ,dp[MX] ,deq[MX] ,sumd[MX] ;
int Cavalue(int j)
{
    return dp[j] + d[j+1] - sumd[j+1] ;
}
void input()
{
   int x ,y ,w ;
   scanf("%d%d" ,&W ,&n) ;
   sumw[0] = sumd[0] = d[0] = 0 ;
   int x1 = 0 ,y1 = 0 ;
   for(int i = 1 ;i <= n ; ++i)
   {
      scanf("%d%d%d" ,&x ,&y ,&w) ;
      sumw[i] = sumw[i-1] + w ;  // 重量的前缀和
      d[i] = abs(x) + abs(y) ;            // 到原点的距离
      sumd[i] = sumd[i-1] + abs(x-x1) + abs(y-y1) ;  // 距离前缀和
      x1 = x ;
      y1 = y ;
   }
}
void DP()
{
   memset(deq ,0 ,sizeof(deq)) ;
   int front = 0 ,end = 0 ;     // deq 双端队列的头和尾
   for(int i = 1 ;i <= n ; ++i)
   {
       while(front < end && Cavalue(deq[end-1]) >= Cavalue(i-1)) // 因为 end 没减一错了好久 
                   end-- ;
       deq[end++] = i-1 ;
       while(front < end && sumw[i] - sumw[deq[front]] > W)
                front++ ;
       dp[i] = Cavalue(deq[front]) + d[i] + sumd[i] ;
   }
}
int main()
{
    int Tx ;
    scanf("%d" ,&Tx) ;
    while(Tx--)
    {
        input() ;
        DP() ;
        cout<<dp[n]<<endl ;
        if(Tx)  cout<<endl ;
    }
    return 0 ;
}

抱歉!评论已关闭.