洛谷 P2904 [USACO08MAR] River Crossing S 题解

题目链接

洛谷 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\),用前缀和求即可。

\[s_i=s_{i-1}+M_i\\ dp_j=\min{(dp_j,dp_{j-i}+s_i+2M)} \]

代码如下,时间复杂度 \(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=\min{(dp_i,dp_j+dp_{i-j}+M)} \]

初始化即为 \(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;
}
posted @ 2026-01-03 19:31  CodingJuRuo  阅读(18)  评论(0)    收藏  举报