是个十足的 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 个)累赘,后面留着用,每次带累赘都要耗一个跑腿。最后是一群人(<=C)过去。
这里直接篡改顺序,按排序后的数组计算,贡献提前计算。
具体地,记 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;
}
你问我怎么想出来的,我认为至少得搞到这个样例的最优解,但我手头上没有可靠的暴力程序。