bzoj1044 [HAOI2008]木棍分割(滚动+后缀和)

来源:互联网 发布:御坂网络 编辑:程序博客网 时间:2024/06/06 08:41

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

分析:

注意:最多砍断m个连接处

在统计答案的时候,每一个i都要统计答案

第一问:二分判定
(这次的二分终于是一遍过啦)

第二问:
f[i][j],切割了i下,切到了j,
设s为长度的前缀和
f[i][j]=sigma(f[i-1][k]) (s[j]-s[k]<=len)
时间复杂度O(mn^2)

显然需要优化

优化空间

显然f[i]只与f[i-1]有关,滚动数组

优化时间

若s[j]-s[k]<=ans,
那么s[j-1]-s[k]<=ans(其中j-1>k)
这样我们计算s[j]的时候,把符合要求的k的f值都加到一个g数组中
同时g维护一个后缀和(这就需要j从大到小循环)
在计算j-1的时候,若当前k < j-1,就可以直接把g[k]-g[j]加入j-1
否则k=j-1,暴力维护新的k值

还有一种我yy的做法(还没实现)
看一下(s[j]-s[k]<=len)
因为s[i]是单增的,若s[j]-s[k]<=len,那么k+1也符合,这就是一个区间和
设j的临界转移点是p(s[j]-s[p]<=len,p最小)
当转移j+1的时候
j+1的最佳转移点不可能在p点的左侧,
我们就可以维护一个双端队列,暴力维护下一个状态需要加的区间

tip

m++

这里写代码片#include<cstdio>#include<iostream>#include<cstring>using namespace std;const int N=50005;const int mod=10007;const int INF=0x33333333;int v[N],s[N];int n,m,mx=0,mn=INF;int f[3][N],len,p[N];int q[N],tou,wei,g[N];void erfen(){    int l=0,r=s[n],mid;    while (l<r)    {        mid=(l+r)>>1;        int tt=1,d=0;        for (int i=1;i<=n;i++)        {            if (v[i]>mid)             {                tt=INF;                break;            }            if (v[i]+d>mid) tt++,d=v[i];            else d+=v[i];        }        if (tt<=m) r=mid;        else l=mid+1;    }    printf("%d ",l);    len=l;}void doit()  //f[i][j],切割了i下,切到了j{    int i,j,k,ans=0;    tou=0,wei=0;    for (i=1;i<=n;i++)         if (s[i]<=len) f[1][i]=1;  //切一下长度符合         else break;    int now=0;  //滚动数组     for (i=2;i<=m;i++)  //切的次数     {        memset(f[now],0,sizeof(f[now]));        memset(g,0,sizeof(g));        k=n+1;        for (j=n;j>1;j--)   //s[j]-s[k]<=ans,        //那么s[j-1]-s[k]<=ans  显然j要从大到小         {            if (j>k) f[now][j]+=(g[k]-g[j])%mod;            else k=j;              //暴力维护             while (k>1&&s[j]-s[k-1]<=len)            {                k--;                f[now][j]=(f[now][j]+f[now^1][k])%mod;                g[k]=(g[k+1]+f[now^1][k])%mod;            }        }        ans+=f[now][n];  //最多砍断m个连接处        now^=1;    }    printf("%d",ans);}int main(){    scanf("%d%d",&n,&m); m++;  //m++    for (int i=1;i<=n;i++)     {        scanf("%d",&v[i]);         s[i]=s[i-1]+v[i];   //前缀和     }    erfen();    int tt=n;    for (int i=n;i>=1;i--)    {        while (i>=1&&s[i]-s[tt-1]<=len) tt--;        p[i]=tt;    }    doit();    return 0;}
原创粉丝点击