[HAOI2008]木棍分割解题报告

305 . [HAOI2008] 木棍分割

★★★☆ 输入文件:stick.in 输出文件:stick.out 简单对比

时间限制:3 s 内存限制:64 MB

【问题描述】

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

【输入格式】

输入文件第一行有2个数n,m

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

【输出格式】

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

对答案mod10007.

【输入样例】

3 2

1

1

10

【输出样例】

10 2

样例说明:两种砍的方法:(1)(1)(10)和(11)(10)

【数据范围】

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

1<=Li<=1000

我一开始写了两遍O(mn)的DP,结果跑了8s多,一看别人的,第一遍都是先二分出来的,时间复杂度

O(mn+nlog2(∑1≤i≤nli?max1≤i≤n{li}))

,加上各种技巧,1s内就可以跑出来.

①这个题给我最大的教训就是要二分的重要性!如果发现了任何单调性,首先一定要想到二分!

二分的话,即使不贪心,也是有O(n)的DP的.

②然后说一下贪心的正确性,假如说最大连续子区间<=mid时存在一种合法方案,那么显然只需要我们可以从最左边开始向右贪心,选择连续区间,如果当前的数可以加到上一个区间里,那么就加,否则就为它新开一个区间,这个思路其实跟活动选择是类似的,因为这样的话我们可以为以后的选择留尽量少的数,而令之后的选择至少不更劣.从而保证了正确性。但是遗憾的是,显然如果有负数的话,这是错误的,因为可能留给后面更多的数反而不更劣.

③如果多次DP其转移的形式都是一样的,而其转移求起来却比较复杂,那么我们大可以一遍预处理出来啊!

Code(纯DP):

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
#define P 10007
#include<algorithm>
inline void in(int &x){
    char c=getchar();
    x=0;
    while(c<‘0‘||c>‘9‘)c=getchar();
    for(;c>=‘0‘&&c<=‘9‘;c=getchar())x=x*10+c-‘0‘;
}
int f[2][50005],s[50005],fs[50005];
int main(){
    freopen("stick.in","r",stdin);
    freopen("stick.out","w",stdout);
    int n,m;
    in(n),in(m);
    int i,j,L;
    for(i=1;i<=n;++i){
        in(L);
        s[i]=s[i-1]+L;
    }
    for(i=1;i<=n;++i)f[0][i]=s[i];
    int p,now;
    f[1][1]=s[1];
    for(i=1;i<=n;++i)f[0][i]=s[i];
    for(j=1;j<=m;++j){
        now=j&1,p=1;
        for(i=2;i<=n;++i){
            while(p+1<i&&f[!now][p+1]<s[i]-s[p+1])++p;
            if(p+1<i)f[now][i]=min(max(f[!now][p],s[i]-s[p]),max(f[!now][p+1],s[i]-s[p+1]));
            else f[now][i]=max(f[!now][p],s[i]-s[p]);
        }
    }
    L=f[m&1][n];
    memset(f,0,sizeof(f));
    int ans=0;
    for(i=1;i<=n&&s[i]<=L;++i)f[0][i]=1;
    for(i=1;i<=n;++i)fs[i]=(fs[i-1]+f[0][i])%P;
    ans=f[0][n];
    for(j=1;j<=m;++j){
        //printf("-----%d-----\n",j);
        p=1,now=j&1,f[now][1]=0;
        for(i=2;i<=n;++i){
            while(s[i]-s[p]>L)++p;
            f[now][i]=(fs[i-1]-fs[p-1])%P;
        }
        memset(fs,0,sizeof(fs));
        for(i=1;i<=n;++i)fs[i]=(fs[i-1]+f[now][i])%P;
        ans=(ans+f[now][n])%P;
    }
    printf("%d %d",L,(ans+P)%P);
}

Code(二分+贪心+DP):

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
#define P 10007
#include<algorithm>
inline void in(int &x){
    char c=getchar();
    x=0;
    while(c<‘0‘||c>‘9‘)c=getchar();
    for(;c>=‘0‘&&c<=‘9‘;c=getchar())x=x*10+c-‘0‘;
}
int f[50005],s[50005],fs[50005],a[50005],pre[50005];
int main(){
    freopen("stick.in","r",stdin);
    freopen("stick.out","w",stdout);
    int n,m;
    in(n),in(m);
    int i,j,L;
    int l=0,r=0,mid,sum,tot;
    for(i=1;i<=n;++i){
        in(a[i]);
        s[i]=s[i-1]+a[i];
        l=max(l,a[i]),r+=a[i];
    }
    --l;
    while(r-l>1){
        mid=l+r>>1;
        tot=0,sum=0;
        for(i=1;i<=n;++i)
            if(sum+a[i]<=mid)sum+=a[i];
            else sum=a[i],++tot;
        if(tot<=m)r=mid;
        else l=mid;
    }
    L=r;
    memset(f,0,sizeof(f));
    int ans=0;
    for(i=1;i<=n&&s[i]<=L;++i)f[i]=1;
    for(i=1;i<=n;++i)fs[i]=(fs[i-1]+f[i])%P;
    ans=f[n];
    int p=1;
    for(i=1;i<=n;++i){
        while(s[i]-s[p]>L)++p;
        pre[i]=p-1;
    }
    for(j=1;j<=m;++j){
        for(i=1;i<=n;++i)f[i]=(fs[i-1]-fs[pre[i]])%P;
        for(i=1;i<=n;++i)fs[i]=(fs[i-1]+f[i])%P;
        ans=(ans+f[n])%P;
    }
    printf("%d %d",L,(ans+P)%P);
}
时间: 2024-11-01 19:10:11

[HAOI2008]木棍分割解题报告的相关文章

BZOJ 1044 木棍分割 解题报告(二分+DP)

来到机房刷了一道水(bian’tai)题.题目思想非常简单易懂(我的做法实际上参考了Evensgn 范学长,在此多谢范学长了) 题目摆上: 1044: [HAOI2008]木棍分割 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 3162  Solved: 1182[Submit][Status][Discuss] Description 有n根木棍, 第i根木棍的长度为Li,n根木棍依次连结了一起, 总共有n-1个连接处. 现在允许你最多砍断m个

BZOJ1044: [HAOI2008]木棍分割

1044: [HAOI2008]木棍分割 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 1580  Solved: 567[Submit][Status] Description 有n根木棍, 第i根木棍的长度为Li,n根木棍依次连结了一起, 总共有n-1个连接处. 现在允许你最多砍断m个连接处, 砍完后n根木棍被分成了很多段,要求满足总长度最大的一段长度最小, 并且输出有多少种砍的方法使得总长度最大的一段长度最小. 并将结果mod 10007.

1044: [HAOI2008]木棍分割

1044: [HAOI2008]木棍分割 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 2161  Solved: 779[Submit][Status][Discuss] Description 有n根木棍, 第i根木棍的长度为Li,n根木棍依次连结了一起, 总共有n-1个连接处. 现在允许你最多砍断m个连接处, 砍完后n根木棍被分成了很多段,要求满足总长度最大的一段长度最小, 并且输出有多少种砍的方法使得总长度最大的一段长度最小. 并将结果m

bzoj1044[HAOI2008]木棍分割 单调队列优化dp

1044: [HAOI2008]木棍分割 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 4314  Solved: 1664[Submit][Status][Discuss] Description 有n根木棍, 第i根木棍的长度为Li,n根木棍依次连结了一起, 总共有n-1个连接处. 现在允许你最多砍断m个连接处, 砍完后n根木棍被分成了很多段,要求满足总长度最大的一段长度最小, 并且输出有多少种砍的方法使得总长度最大的一段长度最小. 并将结果

BZOJ 1044 HAOI2008 木棍分割 二分答案+动态规划

题目大意:给定n个连在一起的木棍,分成m+1段,使每段最大值最小,求最大值的最小值及最大值最小时分割的方案数 第一问水爆了--二分答案妥妥秒过 第二问就有些难度了 首先我们令f[i][j]表示用前j个棒♂子得到i段的方案数 诶我没打什么奇怪的符号吧 于是我们有动规方程 f[i][j]=Σf[i-1][k] (sum[j]-sum[k]<=ans,k<j) 这个最坏情况下是O(m*n^2)的,肯定挂 我们发现k的下界是单调上升的 于是我们直接令k为当前j时k的下界,开一个变量记录k~j的f值之和

[haoi2008]木棍分割

有n根木棍, 第i根木棍的长度为Li, n根木棍依次连结在一起, 总共有n-1个连接处. 现在允许你最多砍断m个连接处, 砍完后n根木棍被分成了很多段,要求满足总长度最大的一段长度最小, 并且输出有多少种砍木棍的方法使得总长度最大的一段长度最小. 第一问:明显的二分答案: 第二问:状态转移方程很容易搞出来:f[i][j]=Σf[k][j-1]   sum[i]-sum[k]<=ans1 看起来是个O(n2m)的dp,但实际上,k的取值只可能是i之前连续的一段,用个q[j-1]表示计算f[i][j

bzoj 1044: [HAOI2008]木棍分割

2016-06-20 第一问是个二分的经典入门题 第二问很容易发现一个DP f[i][j]前i个木棍分j次合法方案数,f[i][j]=f[k][j-1]+...+f[i-1][j-1]; 但这样时间复杂度是O(mn^2),空间复杂度是O(mn) 但我们发现对于相同的j随着i的增加,对应的k也增加,那我们可以根据这个单调性,用前缀和来做到转移复杂度为O(1),空间可以用滚动数组来解决,本来我偷懒用short int开数组,不用滚动数组.结果T了一半,早就听说数组大了会使程序变慢,今天我可算知道了,

【bzoj1044】[HAOI2008]木棍分割 二分+dp

题目描述 有n根木棍, 第i根木棍的长度为Li,n根木棍依次连结了一起, 总共有n-1个连接处. 现在允许你最多砍断m个连接处, 砍完后n根木棍被分成了很多段,要求满足总长度最大的一段长度最小, 并且输出有多少种砍的方法使得总长度最大的一段长度最小. 并将结果mod 10007... 输入 输入文件第一行有2个数n,m.接下来n行每行一个正整数Li,表示第i根木棍的长度.n<=50000,0<=m<=min(n-1,1000),1<=Li<=1000. 输出 输出有2个数,

【BZOJ】1044: [HAOI2008]木棍分割 二分+区间DP

链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1044 Description 有n根木棍, 第i根木棍的长度为Li,n根木棍依次连结了一起, 总共有n-1个连接处. 现在允许你最多砍断m个连接处, 砍完后n根木棍被分成了很多段,要求满足总长度最大的一段长度最小, 并且输出有多少种砍的方法使得总长度最大的一段长度最小. 并将结果mod 10007... Input 输入文件第一行有2个数n,m. 接下来n行每行一个正整数Li,表示第i根木棍