• 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
    ",(ans+10007)%10007);
        return 0;
    }

     

  • 相关阅读:
    微软VS2008月底推出beta 2中文版 搭配.NET 3.5
    Asp.Net AjaxPasswordstrength控件使用
    Asp.Net AjaxHoverMenu控件使用
    Asp.Net Ajax AutoComplete控件使用
    ASP.NET中基类页的设计和使用
    Asp.Net中页面间传值方法
    基于ASP.NET AJAX技术开发在线RSS阅读器(上篇)
    Asp.Net AjaxFilteredTextBox控件使用
    基于ASP.NET AJAX技术开发在线RSS阅读器(下篇)
    Asp.Net AjaxTextBoxWateramrk控件使用
  • 原文地址:https://www.cnblogs.com/Never-mind/p/8117666.html
Copyright © 2020-2023  润新知