ICPC WF 2022 2023 Bridging the Gap 過橋

Zaunese發表於2024-10-16

https://qoj.ac/problem/8683

https://loj.ac/p/6937

是個十足的 DP 題。刷完了 YeahPotato 的 DP 部落格,你覺得有什麼方法能套進來呢?

前面“基於特殊結構的技巧”沒有一個能用。

如何分析性質?分析樣例:

12 3
8 9 9 6 9 9 10 8 8 3 4 5

明顯先排序。

3 4 5 6 8 8 8 9 9 9 9 10

猜想,一定有幾個人在跑腿送燈,一定是前 C 個人的字首,從對岸往回跑的一定是對岸用時最少的。

搓出幾個看上去很全的策略:(| 左側是跑腿,最終都往回跑)

(3,4,5|)(3)(6,8,8)(4)(8,9,9)(5)
(3,4|5)(3)(6,8,8)(4)
(3|4,5)

然而是不對的。實際上最優的是:

  1. 388 過去,3 回來;
  2. 345 過去,3 回來;
  3. 899 過去,4 回來;
  4. 346 過去,3 回來;
  5. 9 9 10 過去,4 回來;
  6. 34 過去。

8+3+5+3+9+4+6+3+10+4+4=59。

發現最終的行走方案不一定等於排序的。並且還是一次帶 k 個跑腿 C-k 個(或 0 個)累贅,後面留著用,每次帶累贅都要耗一個跑腿。最後是一群跑腿過去。

這裡直接篡改順序,按排序後的陣列計算,貢獻提前計算。

具體地,記 fi,j 表示考慮到第 i 個人,運完第 i 個後有 j 個跑腿在對岸待命,此時最小花費。轉移列舉這回幾個人跑腿即可。

最後的一群跑腿直接記在初值裡面。

細節,由於篡改順序,積累的跑腿可以多於 C,但不多於 N/C。轉移為 C,複雜度 O(N×N/C×C)=O(N2)。

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

#define fi first
#define se second
#define mkp std::make_pair
using ll=long long;
using std::max;
using std::min;
template<class T> void cmax(T&a,T b){a=max(a,b);}
template<class T> void cmin(T&a,T b){a=min(a,b);}

const int NV=1e4;

namespace xm{
    ll s[NV+5],f[NV+5][NV+5];
    int a[NV+5];
    void _(){
        int N,C;

        scanf("%d%d",&N,&C);
        for(int i=1;i<=N;++i) scanf("%d",a+i);

        std::sort(a+1,a+N+1);
        for(int i=1;i<=N;++i) s[i]=s[i-1]+a[i];
        if(C>=N){
            printf("%d\n",a[N]);
            return;
        }

        memset(f,0x3f,sizeof f);
        f[0][0]=0;
        for(int i=1;i<=C;++i)f[i][0]=a[i];
        for(int i=0;i<=N;++i)
        for(int j=0;(j-1)*C<=N-i;++j)
        for(int k=0;k<=C&&k<=i;++k){
            if(k>0)
                cmin(f[i][j+k-1],f[i][j]+a[k]+s[k]);
            if(j+k&&k<C)
                cmin(f[min(i+C-k,N)][j+k-1],f[i][j]+a[min(i+C-k,N)]+s[k]);
        }
        printf("%lld\n",f[N][0]);
    }
}

int main(){
    xm::_();
    return 0;
}

相關文章