码迷,mamicode.com
首页 > 其他好文 > 详细

[HAOI2008]木棍分割解题报告

时间:2015-04-21 14:47:33      阅读:204      评论:0      收藏:0      [点我收藏+]

标签:二分   贪心   dp   

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(1inli?max1in{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);
}

[HAOI2008]木棍分割解题报告

标签:二分   贪心   dp   

原文地址:http://blog.csdn.net/ta201314/article/details/45168407

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!