题解/算法 {F - Fuel Round Trip}

題解/算法 {F - Fuel Round Trip}
@LINK: https://atcoder.jp/contests/abc320/tasks/abc320_f;

即路程為P0 -> P1 -> ... -> P[n-1] -> Pn -> P[n-1] -> ... -> P0 長度為3 + 2*(n-1), 但是你無法直接對他進行DP 因為對於P0 -> ... Pi ... -> Pn -> ... Pi ... -> P0 這個Pi 他出現了2次 但是 他倆不是獨立的 是有關聯的, 即對於Pi 他有3種情況 要麼都不加油 要麼在(去程)加油 要麼在(返程)加油;

因此 雖然這個Pi在路程裡出現了2次 但是他倆不是獨立的, 因此 你需要把他倆合併成1個 從而關聯起來, 把路徑寫成如下形式:

            Pi
去程: P0 -> ... -> Pn
返程: P0 <- ... <-

此時 一個Pi 他對應路徑裡的2個點 一個是去程的 一個是返程的;

此時 這裡這個DP 非常的巧妙 他不太容易想到…
先看上面那個錯誤的思路, 路程是3 + 2*(n-1)個點 於是我們按照時間先後關係 這非常符合我們傳統的DP思路, 但是不可行的;
而這裡 我們為了讓Pi給合併起來, DP定義為:

(i,j,k): 從`P0`出發(帶有`H`的初始油量)順時針到達`Pi`(不考慮在Pi加油)時 有`j`油量 且花費為P;
		 從`Pi`出發(帶有`k`的初始油量)逆時針到達`P0`(最終油量`>=0` 即可達) 且花費為S; 
         且讓`DP(i,j,k) = P+S`最小;

這個DP確實非常複雜…
#錯誤理解#: 你可能會認為 既然路徑是形如pre -> ... -> suf 那麼就讓DP去維護pre和suf;
. 這裡錯誤的, 因為 你中間的...是未知的 你一開始的pre|suf 怎麼可以說 他就是某一條路徑的前後綴呢? 你不會有這樣的預知超能力, 比如說 答案是無解的 即不存在合法路徑 那麼 按照你的定義 你的初始狀態全部為INF 可是你一開始怎麼能夠知道 答案是無解的…

仔細理解這個定義, 他並不完全是 答案路徑的前後綴! 他雖然確實維護了兩個路徑pre, suf前綴與後綴, 但是 他更與答案路徑是毫無關係的(這點是樸素DP的思維慣性非常不同), 他是這樣的 非常複雜… 先是前綴P0->...->Pi 且到達Pi點的油量是j 然後是後綴Pi(以k的油量出發)->...->P0 且前綴裡進行過加油的點 不能再加油, 可以發現: [前後綴 他倆有關聯(即加油站最多加油1次)] [但是 他倆也有獨立性 因為前綴結束時的油量j 和後綴出發時的油量k毫無關係的], 這確實是很複雜… 既有關聯 又有獨立性, 可以想象是 你從P0 -> ... -> Pi後 忘掉你此時的油量j 將油量改成k 然後再立刻往回走P0 的最小花費;
#前言#: 每個點有2個狀態{Fir: 剛到達該點(馬上要考慮加油問題), Sec: 馬上要離開該點(已經考慮完加油問題 你無需考慮加油問題)}
. 條件1: j (可以認為是前綴) 定義是: 從起點(滿油)正向出發 到達Pi點的Fir狀態時 還有j的油量 且花費為P;
. 條件2: k(後綴) 定義: 從Pi點的Sec狀態(即不考慮其加油) 以k的初始油量 反向出發 到達P0點 (剩餘油量隨意 只要能到P0即可) 且花費為S;
. 條件3: {1,2,...,i-1}這些加油站 最多加油1次 (要麼不加油 要麼在前綴裡 要麼在後綴裡); (前後綴 這兩次行程 他倆能進行加油的點 是相同的 都是{1,2,...,i-1} (根據定義 Pi是不會進行加油的));
. 條件4: DP(i,j,k)表示 是要讓P+S(即總費用) 最小;

初始狀態: DP( 0, H, 0/1/.../H) = 0;

DP轉移:
i -> i+1 枚舉對Pi加油站的3種情況 {不加油 前綴加油 後綴加油}

d: (Pi,P[i+1])的距離
j: 在Pi的`Fir`狀態的油量
k: 在Pi的`Sec`狀態的油量

如果前綴要加油 即油量會變成`min( j+加油, H)` 然後他需要`>= d` 這樣才能保證可以到達下個點
如果前綴不加油 則如果`j >= d` 則可以到達下個點;

(後綴是最難的... `P[i+1] -> Pi -> ... P0`
如果後綴不加油 則`P[i+1]`的`Sec`狀態的油量 必須是`k+d`(其要滿足`<=H`);
如果後綴加油 令`p: Pi的Fir狀態的油量` 此時有`max( p+加油, H)=k`, 當`p>=0 && p+d<=H`時 更新DP;

於是你得到下面的代碼

int N, H; cin>> N>> H;
Pos[ 0] = 0;
for( int i = 1; i <= N; ++i) cin>> Pos[ i];
for( int i = 1; i < N; ++i) cin>> Cost[ i]>> Add[ i];

memset( DP, 0x3F, sizeof( DP));
for( int i = 0; i <= H; ++i){
    DP[ 0][ H][ i] = 0;
}
for( int i = 0; i < N; ++i){
    for( int j = 0; j <= H; ++j){
        for( int k = 0; k <= H; ++k){
            auto & curDP = DP[i][j][k];
            if( curDP == INT32_0x3F){ continue;}
            auto dist = Pos[ i + 1] - Pos[ i];
            if( i == 0){
                if( j >= dist  &&  k+dist <= H){
                    DP[ i+1][ j-dist][ k+dist] = 0;
                }
                continue;
            }
            { // no use
                if( j >= dist  &&  k+dist <= H){
                    MinSelf_( DP[ i+1][ j-dist][ k+dist], curDP);
                }
            }
            { // use in OutBound
                auto new_j = min( j + Add[ i], H);
                if( new_j >= dist  &&  k+dist <= H){
                    MinSelf_( DP[ i+1][ new_j-dist][ k+dist], curDP + Cost[ i]);
                }
            }
            { // use in Return
                auto new_k = k - Add[ i];
                if( j >= dist  &&  new_k >= 0  &&  new_k+dist <= H){
                    MinSelf_( DP[ i+1][ j-dist][ new_k+dist], curDP + Cost[ i]);
                }
            }
        }
    }
}
int ANS = INT32_0x3F;
for( int j = 0; j <= H; ++j){
    MinSelf_( ANS, DP[ N][ j][ j]);
}
if( ANS == INT32_0x3F){ ANS = -1;}
cout<< ANS<< ED_;

看似很正確, 卻疏忽了一點…
上面在分析後綴加油時的那個p(也就是代碼裡的new_k), 你認為 他就是k - 加油;
. 首先看下前綴的加油, 因為他是正向的, 因此 加油後 他的加油就變成了min( j+加油, H);
. 於是你照貓畫虎 後綴的加油 就是j - 加油, 這是錯誤的… 因為 後綴他是反向的, 即new_k -> 加油 -> k, 如果k < H 此時的new_k 確實只有一種可能k - 加油 這是沒問題的; 可以當k=H時 他的new_k是有多種可能的! 比如加油=4, k=H=5 此時new_k = {1,2,3,4,5}都是可以的, 因此 這裡不像(前綴加油那樣) 這裡是有很多種可能性的 因此需要進行for循環 一個個特判;

{ // use in Return
    if( k == H){
        for( int new_k = H-1; new_k >= 0  &&  new_k + Add[i] >= H; --new_k){
            if( j >= dist  &&  new_k+dist <= H){
                MinSelf_( DP[ i+1][ j-dist][ new_k+dist], curDP + Cost[ i]);
            }
    	}
	}
	else{
    	auto new_k = k - Add[ i];
    	if( j >= dist  &&  new_k >= 0  &&  new_k+dist <= H){
    		MinSelf_( DP[ i+1][ j-dist][ new_k+dist], curDP + Cost[ i]);
   	 	}
    }
}

注意 看似是4層循環 但因此最後那個是k==H的特判 即可以認為是N* H * (H + H);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值