【bzoj1044】木棍分割

时间:2022-05-12 07:51:42

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

Solution

第一问我们使用二分查找查询每个区间的长度上线是多少

对于第二问,我们使用动态规划

f[i][j]=simga(f[k][j-1])(s[i]-s[k]<=lim)

显然的我们可以使用前缀和来解决问题

然后对于每个i,k最小能取到多少是确定的,我们预处理一下,

然后加个滚动数组就一遍a了,开心!

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<string>
#include<string.h>
#include<algorithm>
#include<math.h>
#include<queue>
#include<map>
#include<vector>
#include<set>
#define il inline
#define re register
#define mod 10007
using namespace std;
const int N=;
int n,m,a[N],l=,r=,mid,s[N],lim,p[N],f[][N],lst,now,ans;
il bool chk(int lim){
int cnt=,tot=;
for(int i=;i<=n;i++){
if(cnt+a[i]<=lim){
cnt+=a[i];
}
else{
cnt=a[i];tot++;
}
}
return tot<=m;
}
int main(){
scanf("%d%d",&n,&m);m++;
for(int i=;i<=n;i++){
scanf("%d",&a[i]);
r+=a[i];l=max(l,a[i]);
} while(l<r){
mid=(l+r)/;
if(chk(mid)) r=mid;
else l=mid+;
}
lim=r;
for(int i=;i<=n;i++) s[i]=s[i-]+a[i];
for(int i=;i<=n;i++){
l=;r=n;
while(l<r){
mid=(l+r)/;
if(s[i]-s[mid-]<=lim) r=mid;
else l=mid+;
}
p[i]=r;
}
lst=;now=;
for(int i=;i<=n;i++)
if(s[i]<=lim) f[lst][i]=f[lst][i-]+;
else f[lst][i]=f[lst][i-];
ans=(f[][n]-f[][n-])%mod;
for(int j=;j<=m;j++){
memset(f[now],false,sizeof(f[now]));
for(int i=j,x;i<=n;i++){
x=(f[lst][i-]-f[lst][max(p[i]-,)])%mod;
f[now][i]=(f[now][i-]+x)%mod;
}
ans=(ans+f[now][n]-f[now][n-]+mod)%mod;
swap(now,lst);
}
cout<<lim<<" "<<ans;
return ;
}