首先,可以感性地发现挪馅饼时出现负值不会影响最终答案,只要最终方案是非负的就行了。
所以,我们不妨规定,一个盘子只能从右边一个盘子拿馅饼,或者向右边一个盘子放馅饼。
我们用 fi,j,k 表示前 i 盘放 j 张馅饼,第 i 盘放 k 张馅饼的最小花费。不难发现 fi,j,k 可以转移到 fi+1,j+p,p 。
现在我们记原来前 i 个盘子上的馅饼和为 Si 。
考虑如何从 fi,j,k 转移到 fi+1,j+p,p :由于我们每次转移馅饼都只和右边一个盘子有关,所以前 i 个盘子定好后的状态中,前 i+1 个盘子上的馅饼数之和依然是 Si+1 。
所以我们只需从第 i+2 个盘子里拿 j+p−Si+1 个馅饼到第 i+1 个盘子上就行了(若拿负数个馅饼过来,等同于拿出去这么多个)。于是状态转移方程就推出来了:
fi+1,j+p,p=min{fi+1,j+p,p,fi,j,k+∣j+p−Si+1∣}
枚举 i,j,k,p ,复杂度 O(n×m3) ,会超时。
考虑到 fi+1,j+p,p 会被所有 fi,j,k(k≥p) 都转移一次,故可以倒着枚举 k :设当前枚举到 i,j,k , minn=minh=kmfi,j,h ,则有转移方程:
fi+1,j+k,k=min{minn+∣j+p−Si+1∣}
最终答案 ans 为:
ans=mink=0m{fn,m,k}
时间复杂度 O(n×m2) ,可以通过此题。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #include<icecream> #include<cstring> #define abs(x) (x<0?-(x):x) using namespace std; const int N=250,INF=0x3f3f3f3f; int f[N+3][N+3][N+3],n,m,a[N+3],ans=INF; inline void tomin(int& x,const int y){ if (y<x) x=y; } signed main(){ cin>>n>>m; for (int i=1;i<=n;++i) cin>>a[i],a[i]+=a[i-1]; memset(f,0x3f,sizeof(f)); f[0][0][m]=0; for (int i=0;i<n;++i){ for (int j=0;j<=m;++j){ int minn=INF; for (int k=m;k>=0;--k){ tomin(minn,f[i][j][k]); if (j+k<=m) tomin(f[i+1][j+k][k],minn+abs(j+k-a[i+1])); } } } for (int k=0;k<=m;++k) tomin(ans,f[n][m][k]); return cout<<ans,0; }
|