P4244【HAOI2008】木棍分割

来源:互联网 发布:激光脉冲价格知乎 编辑:程序博客网 时间:2024/05/16 05:28

问题描述

有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.

题解

第一问裸二分答案
第二问dp
设状态dp[i][j]表示前j个砍i刀的方案总数,所以dp[i][j] = sum(dp[i-1][k(sum[j]-sum[k]<=maxx),考虑到起为和的形式可以用一个数组g来维护缀和,又因为其只与i-1有关,所以可以考虑滚动数组。

注意将最终答案加上mod再%mod

代码

#include<stdio.h>  #include<algorithm>  #include<cstdio>  #include<cmath>  #include<iostream>  using namespace std;  int n,m;  #define maxn 50005#define mod 10007  int f[2][maxn],d[maxn],sum[maxn]; int maxx,ans;  bool ok(int x)  {      int i,j,tot=0,cnt=0;      for(i=1;i<=n;i++)      {          tot+=d[i];        if(tot>x){            tot=d[i];            cnt++;        }      }      if(cnt>m) return false;      else return true;  } int g[2][maxn];void dp() {     int i,j,k;     for(i=1;i<=n;i++) {    if(sum[i]<=maxx) f[0][i]=1;    g[0][i]=g[0][i-1]+f[0][i];}     for(i=1;i<=m;i++)     {         int l=i,now=i&1,las=now^1;          for(j=i+1;j<=n;j++)         {            while(sum[j]-sum[l]>maxx) l++;            f[now][j]=(g[las][j-1]-g[las][l-1])%mod;            //if(f[now][j]<0) cout<<f[now][j];            g[now][j]=(f[now][j]+g[now][j-1])%mod;        }         ans+=f[now][n];        ans%=mod;     } }  int main()  {      scanf("%d%d",&n,&m);      int i,j;    int maxxx=0;      for(i=1;i<=n;i++) {scanf("%d",&d[i]);sum[i]=sum[i-1]+d[i];maxxx=max(d[i],maxxx);};      int l=maxxx,r=sum[n];      while(l<r)      {          int mid=l+r>>1;          if(ok(mid)) r=mid;          else l=mid+1;      }      for(i=r-2;;i++) if(ok(i)){maxx=i;break;    }     dp();     cout<<maxx<<" "<<(ans+mod)%mod;  }