题目链接
洛谷 P2904 [USACO08MAR] River Crossing S
F1:背包 DP
首先观察题目条件。由于需要 \(n\) 头奶牛全部过河,求最小时间,所以可以认为假设有 \(k\) 次过河,第 \(i\) 次过河过 \(a_i\) 头奶牛,那么 \(a_1+a_2+\dots +a_k=n\)。所以可以看成有 \(n\) 件物品,表示本次过河带几头奶牛,如果以奶牛数量为代价,所需时间为价值,则第 \(i\) 件物品代价为 \(i\),价值为 \(2M+M_1+M_2+\dots+M_i\),即过河时间加上一来一回。
所以设 \(dp_j\) 表示 \(j\) 头奶牛过河同时约翰把木筏划回来的最少时间。由于每次带几头奶牛是任意的,所以是完全背包,状态转移方程如下,其中 \(i\) 枚举“物品”,\(j\) 枚举奶牛头数,\(s_i=M_1+M_2+\dots+M_i\),用前缀和求即可。
代码如下,时间复杂度 \(O(n^2)\),具体细节与普通完全背包类似。
#include<bits/stdc++.h>
using namespace std;const int N=2505;
int n,m;
int a[N],s[N],dp[N];int main(){scanf("%d%d",&n,&m);for (int i=1;i<=n;++i) scanf("%d",a+i);for (int i=1;i<=n;++i) s[i]=s[i-1]+a[i];memset(dp,0x3f,sizeof dp);dp[0]=0;for (int i=1;i<=n;++i){for (int j=i;j<=n;++j) dp[j]=min(dp[j],dp[j-i]+s[i]+2*m);}printf("%d",dp[n]-m);return 0;
}
F2:区间 DP
假设原本有 \(x\ (x>1)\) 头奶牛一起过江,那么除了让它们一起过江,还可以把它们分成前 \(y\) 头奶牛,后 \(x-y\) 头奶牛分别过江。假设 \(i\) 头奶牛过江所需最少时间为 \(dp_i\),那么如果不分开,所需时间为 \(dp_x\);如果按上述方案分开过江,就变为 \(dp_y\) 和 \(dp_{x-y}\) 两个子问题,所需时间为分别所需时间 \(dp_y,dp_{x-y}\) 再加上船空载一程所需时间 \(M\)。综上所述,状态转移方程如下,其中 \(i\) 枚举奶牛头数,\(j\) 枚举割点,即前一部分奶牛头数。
初始化即为 \(dp_i=\begin{cases}M&,i=0\\M+M_1+M_2+\dots+M_i=dp_{i-1}+M_i&, i\ge1\end{cases}\),用前缀和求得。代码如下,时间复杂度 \(O(n^2)\)。
#include<bits/stdc++.h>
using namespace std;const int N=2505;
int n,m;
int a[N],dp[N];int main(){scanf("%d%d",&n,&m);for (int i=1;i<=n;++i) scanf("%d",a+i);dp[0]=m;for (int i=1;i<=n;++i) dp[i]=dp[i-1]+a[i];for (int i=1;i<=n;++i){for (int j=1;j<i;++j) dp[i]=min(dp[i],dp[j]+dp[i-j]+m);}printf("%d",dp[n]);return 0;
}