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

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;
}
posted @ 2017-08-10 14:10  wtt3117  阅读(82)  评论(0编辑  收藏