HAOI2008 木棍分割 二分答案 前缀和优化 单调队列 滚动数组

NKOJ4244 HAOI2008 木棍分割

问题描述

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

输入格式

第一行有2个数n,m. 接下来n行每行一个正整数Li,表示第i根木棍的长度.

输出格式

2个数, 第一个数是总长度最大的一段的长度最小值, 第二个数是有多少种砍的方法使得满足条件.

样例输入

3 2
1 1 10

样例输出

10 2

样例说明

两种砍的方法: (1)(1)(10)和(1 1)(10)

数据范围

n<=50000, 0<=m<=min(n-1,1000).
1<=Li<=1000.


这道题的综合性比较强。考察了很多知识点,尤其是对于优化的考察。但是其实并没有太复杂。

第一个问题,“长度最大的最小”,显然的二分答案标志。轻松水过。

第二个问题,考虑递推。定义状态 f[i][j] 表示前i根木棍切j刀,且第j刀切在i处的方案总数,那么有

f[i][j]=f[k][j1],sum[i]sum[k]Ans
Ans 为上一个问题的答案。这当然是正解,然而我们注意到,时间复杂度 O(mn2) ,空间复杂度 O(mn) ,从哪个方面都过不了。

所以我们需要优化。滚动数组的优化是很容易想到的,因为注意到 f[i][j] 只与第二维是 j1 的状态有关系。

递推式里面有多个连续的数求和的形式,考虑前缀和优化。但是这里有个问题:如何定位 k ?注意到sum数组单调递增,差值小于等于一个定值,这是显然的滑动窗口模型。那么可以用单调队列优化。

优化之后,时间复杂度 O(mn) ,空间复杂度 O(2m) ,就能够AC了。


代码:

#include<stdio.h>
using namespace std;

const int mod=10007,MAXN=50005;

int N,M,Ans,sum[MAXN],Max,f[MAXN],sumf[MAXN][2];
int Q[MAXN],head,tail;

inline int _R()
{
    char s=getchar();int v=0,sign=0;
    while((s!='-')&&(s>57||s<48))s=getchar();
    if(s=='-')sign=1,s=getchar();
    for(;s>47&&s<58;s=getchar())v=v*10+s-48;
    if(sign)v=-v;
    return v;
}

bool check(int x)
{
    int cnt=0,i,las=0;

    for(i=1;i<=N;i++)
    {
        if(sum[i]-sum[las]>x)
        {
            cnt++;
            las=i-1;
        }
        if(cnt>M)return false;
    }
    return true;
}

int main()
{
    int L,R=0,mid,i,j,x,y,t;

    N=_R();M=_R();
    for(i=1;i<=N;i++)
    {
        x=_R(),sum[i]=sum[i-1]+x;
        Max=Max<x?x:Max;
    }
    L=Max;R=sum[N];

    while(L<=R)
    {
        mid=L+R>>1;
        if(check(mid))R=mid-1;
        else L=mid+1;
    }

    printf("%d ",L);

    for(i=1;i<=N;i++)
    {
        if(sum[i]<=L)f[i]=1;
        sumf[i][1]=sumf[i-1][1]+f[i];
    }
    Ans+=f[N];

    for(j=1;j<=M;j++)
    {
        head=0;
        x=j&1;y=x^1;
        for(i=1;i<=N;i++)
        {
            while(sum[i]-sum[head]>L)head++;
            t=head;
            if(t)t--;
            f[i]=(sumf[i-1][x]-sumf[t][x])%mod;
            sumf[i][y]=(sumf[i-1][y]+f[i])%mod;
        }
        Ans=(Ans+f[N])%mod;
    }

    printf("%d",(Ans+mod)%mod);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值