竞赛生TomChen 2026-03-08 16:41 采纳率: 60%
浏览 11

编程提问0001算法题

题目:
分割木棍
题目描述
有 n 根木棍,第 i 根木棍的长度为 Li,n 根木棍依次连结了一起,总共有 n-1 个连接处。现在允许你最多砍断 m 个连接处,砍完后 n 根木棍被分成了很多段,要求满足总长度最大的一段长度最小,并且输出有多少种砍的方法使得总长度最大的一段长度最小。并将结果 mod 10007。
输入格式
输入文件第一行有 2 个数 n,m。接下来 n 行每行一个正整数 Li,表示第 i 根木棍的长度。
输出格式
输出有 2 个数,第一个数是总长度最大的一段的长度最小值,第二个数是有多少种砍的方法使得满足条件。
输入样例
3 2
1
1
10
输出样例
10 2
提示
两种砍的方法:(1)(1)(10) 和 (1 1)(10)
数据范围
n≤50000,0≤m≤min(n-1,1000)
1≤Li≤1000
我的代码:

#include<bits/stdc++.h>
#define P 10007
#define mid (l+r>>1)
using namespace std;
const int N=5005;
int n,m,l,r,k,s,t,S[N],a[N],f[N][1005],sum[N][1005];
int check(int M){
    int now=0,tot=1;
    for(int i=1;i<=n;++i){
        if(now+a[i]<=M)now+=a[i];
        else {
            now=a[i];
            tot++;
        }
    }
    if(tot<=m)return 1;
    else return 0;
}
int main(){
    cin>>n>>m;
    m++;
    for(int i=1;i<=n;++i){
        cin>>a[i];
        S[i]=S[i-1]+a[i];
        l=max(l,a[i]);r+=a[i];
    }
    while(l<=r){
        if(check(mid))r=mid-1;
        else l=mid+1;
    }
    int ans=l,tmp=0;
    f[0][0]=1;
    for(int i=0;i<=n;++i) sum[i][0]=1;
    int k=-1;
    for(int i=1;i<=n;++i){
        while(S[i]-S[k+1]>ans)
            ++k;
        for(int j=1;j<=min(i,m);++j){
            f[i][j]=(sum[i-1][j-1]-(k==-1?0:sum[k][j-1]))%P;
            sum[i][j]=(sum[i-1][j]+f[i][j])%P;
        }
    }
    for(int j=1;j<=m;++j) tmp=(tmp+f[n][j])%P;
    cout<<ans<<' '<<(tmp+P)%P;
}

  • 写回答

4条回答 默认 最新

  • 檀越@新空间 2026-03-08 16:42
    关注

    下午好🌅🌅🌅
    本答案参考通义千问

    你提供的代码是用于解决“分割木棍”问题的,但其中存在一些逻辑错误和实现上的问题。我们来逐步分析并给出解决方案。


    一、题目理解

    题目要求:

    • 给定 n 根木棍,长度为 L_i,依次相连。
    • 允许最多砍断 m 个连接处(即最多分成 m+1 段)。
    • 要求总长度最大的一段的长度最小,并且输出满足这个条件的分割方法数,模上 10007。

    二、关键点分析

    1. 确定最小的最大段长度

    这是典型的二分查找 + 贪心判断的问题。

    • 使用二分法找到最小的 max_len,使得可以将木棍分成不超过 m+1 段,且每段长度 <= max_len
    • 这一步是正确的,但要注意边界条件。

    2. 计算满足条件的分割方法数

    这部分是难点,你的代码中使用了动态规划(DP)和前缀和数组,但存在以下问题:

    • f[i][j] 表示前 i 根木棍,分成 j 段的方案数。
    • sum[i][j] 是前缀和,表示前 i 根木棍分成 j 段的方案总数。
    • 但你在循环中对 k 的处理不正确,导致无法正确统计所有合法分割方式。

    三、修改后的完整代码

    #include <bits/stdc++.h>
    using namespace std;
    
    const int MOD = 10007;
    const int MAXN = 50005;
    const int MAXM = 1005;
    
    int n, m;
    int a[MAXN];
    long long S[MAXN]; // 前缀和数组
    long long dp[MAXN][MAXM]; // dp[i][j]: 前i根木棍分成j段的方案数
    long long sum[MAXN][MAXM]; // sum[i][j]: 前i根木棍分成j段的方案总数
    
    // 判断是否可以将木棍分成<=m段,每段长度<=mid
    bool check(long long mid) {
        long long cur = 0;
        int cnt = 1;
        for (int i = 1; i <= n; ++i) {
            if (cur + a[i] > mid) {
                cur = a[i];
                cnt++;
            } else {
                cur += a[i];
            }
        }
        return cnt <= m + 1;
    }
    
    int main() {
        cin >> n >> m;
        for (int i = 1; i <= n; ++i) {
            cin >> a[i];
            S[i] = S[i - 1] + a[i];
        }
    
        // 二分查找最小最大段长度
        long long l = 0, r = S[n];
        while (l < r) {
            long long mid = (l + r) / 2;
            if (check(mid)) {
                r = mid;
            } else {
                l = mid + 1;
            }
        }
        long long ans = l;
    
        // 初始化 DP 数组
        dp[0][0] = 1;
        for (int i = 0; i <= n; ++i) {
            sum[i][0] = 1;
        }
    
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= m + 1; ++j) {
                // 找到 k,使得 S[i] - S[k] <= ans
                int left = 0, right = i - 1;
                int k = 0;
                while (left <= right) {
                    int mid = (left + right) / 2;
                    if (S[i] - S[mid] <= ans) {
                        k = mid;
                        left = mid + 1;
                    } else {
                        right = mid - 1;
                    }
                }
                // dp[i][j] = sum_{k} dp[k][j-1]
                dp[i][j] = (sum[i - 1][j - 1] - (k == 0 ? 0 : sum[k - 1][j - 1])) % MOD;
                if (dp[i][j] < 0) dp[i][j] += MOD;
                sum[i][j] = (sum[i - 1][j] + dp[i][j]) % MOD;
            }
        }
    
        // 答案是最后的 dp[n][m+1],因为最多允许 m 次切割,分成 m+1 段
        cout << ans << " " << dp[n][m + 1] % MOD << endl;
    
        return 0;
    }
    

    四、重点部分说明(加粗)

    1. 二分查找部分

    • 我们通过二分法找出最小的最大段长度,保证在允许的切割次数下,可以将木棍分割成尽可能小的段。

    2. 动态规划与前缀和优化

    • dp[i][j] 表示前 i 根木棍被分成 j 段的方案数。
    • sum[i][j]dp[1..i][j] 的前缀和,用于快速计算区间和。
    • 通过二分查找找到 k,使得 S[i] - S[k] <= ans,从而确定可能的分割位置。

    3. 最终答案

    • 第一个数是 ans,即最小的最大段长度。
    • 第二个数是 dp[n][m+1],即分割成 m+1 段的方案数。

    五、总结

    | 问题 | 解决方案 | |------|----------| | 最小最大段长度 | 二分查找 + 贪心判断 | | 分割方案数 | 动态规划 + 前缀和优化 | | 时间复杂度 | O(n * m * log(S)),适用于 n ≤ 50000,m ≤ 1000 |


    如果你还有其他问题或需要进一步解释,请随时告诉我!

    评论

报告相同问题?

问题事件

  • 创建了问题 3月8日