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)
然而是不對的。實際上最優的是:
- 388 過去,3 回來;
- 345 過去,3 回來;
- 899 過去,4 回來;
- 346 過去,3 回來;
- 9 9 10 過去,4 回來;
- 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;
}