[bzoj1044][DP]木棍分割

本文介绍了一种通过二分查找和动态规划解决的算法问题,旨在寻找将一系列长度不等的木棍切割成多段时,如何使得其中最长一段的长度最小,并计算达到此最优解的不同切割方式的数量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

  有n根木棍, 第i根木棍的长度为Li,n根木棍依次连结了一起, 总共有n-1个连接处. 现在允许你最多砍断m个连 接处,
砍完后n根木棍被分成了很多段,要求满足总长度最大的一段长度最小, 并且输出有多少种砍的方法使得总长 度最大的一段长度最小. 并将结果mod
10007。。。

Input

  输入文件第一行有2个数n,m.接下来n行每行一个正整数Li,表示第i根木棍的长度.n<=50000,0<=m<=min(n-1,10
00),1<=Li<=1000.

Output

  输出有2个数, 第一个数是总长度最大的一段的长度最小值, 第二个数是有多少种砍的方法使得满足条件.

Sample Input

3 2
1
1
10

Sample Output

10 2

HINT

两种砍的方法: (1)(1)(10)和(1 1)(10)

题解

第一问二分答案On判定很水吧。。
第二问设一个dp
f[i][j]表示1~i,切了j刀的方案数
方程就是f[i][j]=sigma(f[k][j-1]),满足k+1~i这一段<=第一问的答案
考虑优化
空间上很明显可以看出,f[i][j]只会与f[k][j-1]有关,所以可以滚动一下
时间上,用一个前缀和优化一下,单调队列的思想用一下可以做到nm吧
具体可以看代码可读性很高吧
前缀和千万不要取模。。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;
const int mod=10007;
int n,m;
int a[51000];
bool check(int p)
{
    int tmp=0,len=0;
    for(int i=1;i<=n;i++)
    {
        len+=a[i];
        if(len>p)tmp++,len=a[i];
    }
    if(len>p)return false;
    if(tmp>m)return false;
    return true;
}
int f[2][51000];//1~j成一段了 
int sum[51000],fro[51000];
int main()
{
    scanf("%d%d",&n,&m);
    int s=0;
    for(int i=1;i<=n;i++){scanf("%d",&a[i]);s+=a[i];fro[i]=fro[i-1]+a[i];}
    int l=1,r=s,ans;
    while(l<=r)
    {
        int mid=(l+r)/2;
        if(check(mid)){ans=mid;r=mid-1;}
        else l=mid+1;
    }
    printf("%d ",ans);
    int st=0,ret=0;s=0;
    for(int i=1;i<=n;i++)
    {
        s+=a[i];
        if(s<=ans)f[st][i]=1;
        else break;
    }
    sum[0]=0;
    for(int i=1;i<=n;i++)sum[i]=(sum[i-1]+f[st][i])%mod;
    for(int i=1;i<=m;i++)
    {
        st^=1;memset(f[st],0,sizeof(f[st]));
        int u=0;
        for(int j=1;j<=n;j++)
        {
            while(fro[j]-fro[u]>ans)u++;
            if(u!=0)f[st][j]=(f[st][j]+sum[j-1]-sum[u-1])%mod;
            else f[st][j]=(f[st][j]+sum[j-1])%mod;
        }
        /*for(int j=1;j<=n;j++)
        {
            int sum=a[j];
            for(int k=j-1;k>=0;k--)
            {
                if(sum>ans)break;
                f[st][j]=(f[st][j]+f[st^1][k])%mod;
                sum+=a[k];
            }
        }*/
        sum[0]=0;
        for(int j=1;j<=n;j++)sum[j]=(sum[j-1]+f[st][j]);
        ret=(ret+f[st][n])%mod;
    }
    printf("%d\n",ret);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值