[BZOJ1044][HAOI2008]木棍分割(二分+贪心+dp)

题目描述

传送门

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

题解

第一问贪心+二分判定
判定出了答案之后dp一下,f(i,j)表示砍了i刀,砍到第j个的方案数,单调地扫一遍预处理出第i个位置最远可以从哪里转移,然后用前缀和优化一下+滚动数组就行了

代码

#include
#include
#include
#include
#include
using namespace std;
#define Mod 10007
#define N 50005

int n,m,Max,Min,ans1,ans2;
int a[N],sa[N],last[N],f[2][N],s[2][N];

bool check(int mid)
{
    int now,cnt=0;
    for (int i=0,j;i<=n;i=j)
    {
        j=i;now=0;
        while (j<=n&&now+a[j]<=mid)
            now+=a[j],++j;
        ++cnt;
    }
    return cnt<=m+1;
}
int find()
{
    int l=Min,r=Max,mid,ans;
    while (l<=r)
    {
        mid=(l+r)>>1;
        if (check(mid)) ans=mid,r=mid-1;
        else l=mid+1;
    }
    return ans;
}
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;++i)
    {
        scanf("%d",&a[i]);sa[i]=sa[i-1]+a[i];
        Max+=a[i],Min=max(Min,a[i]);
    }
    ans1=find();
    int p=0;
    for (int i=1;i<=n;++i)
    {
        while (sa[i]-sa[p]>ans1) ++p;
        last[i]=p;
        if (!last[i]) f[1][i]=1;
    }
    for (int i=1;i<=n;++i) s[1][i]=(s[1][i-1]+f[1][i])%Mod;
    for (int i=2;i<=m+1;++i)
    {
        memset(f[i&1],0,sizeof(f[i&1]));
        memset(s[i&1],0,sizeof(s[i&1]));
        for (int j=i;j<=n;++j)
        {
            f[i&1][j]=(f[i&1][j]+s[(i-1)&1][j-1])%Mod;
            if (last[j]) f[i&1][j]=(f[i&1][j]-s[(i-1)&1][last[j]-1]+Mod)%Mod;
            s[i&1][j]=(s[i&1][j-1]+f[i&1][j])%Mod;
        }
        ans2=(ans2+f[i&1][n]);
    }
    printf("%d %d\n",ans1,(ans2+Mod)%Mod);
}

你可能感兴趣的:(题解,dp,贪心,省选,二分)