BZOJ1044: [HAOI2008]木棍分割

【传送门:BZOJ1044


简要题意:

  给出n个数,求出最多分成m+1段的最长段的最小值,并且求出能分成最长段最小的情况数


题解:

  一道思维题(好吧,就是搞了我一晚上的题)

  首先最小值我们可以用二分来搞出来,二分最小值,然后从头开始,一直累加,如果当前累加值加上a[i]超过了二分出来的值的话,就新开一个段,并且把a[i]放在这个段的开头,最后如果分成的段小于等于m+1的话,就证明这个最小值是行得通的

  其实求最小值并不是难处,关键是怎么求情况数

  很容易想到设f[i][j]为到第i个数时分成j段的情况数

  设s[i]为$\sum_{j=1}^{i}a[j]$

  可以得到方程$$f[i][j]=\sum_{s[i]-s[k]<=d}(i>k)f[k][j-1]$$

  但是这样O(mn^2)肯定会T,而且空间也过大

  那么怎么优化呢?

  我们发现其实f[][j]只与f[][j-1]有关系,那么我们可以用滚动数组来优化空间

  然后其实a数组和s数组是保持不变的,也就是说对于i这个位置而言,往前面延伸,能延伸最前面的位置设为k[i],也就是当s[i]-s[k]<=d时,k[i]就表示为k的最小值

  那么我们就可以把方程转化为$$f[i][q]=\sum_{s[i]-s[k]<=d}(i>k)f[k][p]$$

  p表示上一次的状态,q表示现在要处理的状态

  其实我们看得出来可以用前缀和来得到$\sum_{s[i]-s[k]<=d}(i>k)f[k][p]$

  时间就可以优化到O(nm)了

  接下来是些小细节:对于一开始f[0][0]要等于1,因为后面的数有可能只分成一段,所以用来特判这种情况


参考代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int l[51000],m,n;
int s[51000];
int sum[51000];
int f[2][51000];
int k[51000];
bool check(int d)
{
    int dd=0;int mm=1;
    for(int i=1;i<=n;i++)
    {
        if(dd+l[i]>d)
        {
            dd=l[i];
            if(dd>d) return false;
            mm++;if(mm>m) return false;
        }
        else dd+=l[i];
    }
    return true;
}
int main()
{
    scanf("%d%d",&n,&m);m++;
    s[0]=0;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&l[i]);
        s[i]=s[i-1]+l[i];
    }
    int l=1,r=s[n];
    int d=0;
    while(l<=r)
    {
        int mid=(l+r)/2;
        if(check(mid)==true)
        {
            d=mid;
            r=mid-1;
        }
        else l=mid+1;
    }
    printf("%d ",d);
    memset(f,0,sizeof(f));
    l=1;r=1;
    while(r<=n)
    {
        while(s[r]-s[l-1]>d) l++;
        k[r]=l;
        r++;
    }
    int p=0;
    int ans=0;
    f[0][0]=1;
    for(int i=1;i<=m;i++)
    {
        int q=1-p;
        sum[0]=f[p][0];
        for(int j=1;j<=n;j++) sum[j]=(sum[j-1]+f[p][j])%10007;
        f[q][0]=0;
        for(int j=i;j<=n;j++)
        {
            int d;
            if(k[j]==1) d=sum[j-1];
            else d=sum[j-1]-sum[k[j]-2];
            f[q][j]=(d+10007)%10007;
        }
        ans=(ans+f[q][n])%10007;
        p=q;
    }
    printf("%d\n",(ans+10007)%10007);
    return 0;
}

 

posted @ 2017-12-26 19:34  Star_Feel  阅读(260)  评论(0编辑  收藏  举报