题目描述
题目大意:有n根木棍, 第i根木棍的长度为Li,n根木棍依次连结了一起, 总共有n-1个连接处. 现在允许你最多砍断m个连接处, 砍完后n根木棍被分成了很多段,要求满足总长度最大的一段长度最小, 并且输出有多少种砍的方法使得总长度最大的一段长度最小. 并将结果mod 10007。
题解
第一问贪心+二分判定
判定出了答案之后dp一下,f(i,j)表示砍了i刀,砍到第j个的方案数,单调地扫一遍预处理出第i个位置最远可以从哪里转移,然后用前缀和优化一下+滚动数组就行了
代码
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define Mod 10007
#define N 50005
int n,m,Max,Min,ans1,ans2;
int a[N],sa[N],last[N],f[2][N],s[2][N];
bool check(int mid)
{
int now,cnt=0;
for (int i=0,j;i<=n;i=j)
{
j=i;now=0;
while (j<=n&&now+a[j]<=mid)
now+=a[j],++j;
++cnt;
}
return cnt<=m+1;
}
int find()
{
int l=Min,r=Max,mid,ans;
while (l<=r)
{
mid=(l+r)>>1;
if (check(mid)) ans=mid,r=mid-1;
else l=mid+1;
}
return ans;
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;++i)
{
scanf("%d",&a[i]);sa[i]=sa[i-1]+a[i];
Max+=a[i],Min=max(Min,a[i]);
}
ans1=find();
int p=0;
for (int i=1;i<=n;++i)
{
while (sa[i]-sa[p]>ans1) ++p;
last[i]=p;
if (!last[i]) f[1][i]=1;
}
for (int i=1;i<=n;++i) s[1][i]=(s[1][i-1]+f[1][i])%Mod;
for (int i=2;i<=m+1;++i)
{
memset(f[i&1],0,sizeof(f[i&1]));
memset(s[i&1],0,sizeof(s[i&1]));
for (int j=i;j<=n;++j)
{
f[i&1][j]=(f[i&1][j]+s[(i-1)&1][j-1])%Mod;
if (last[j]) f[i&1][j]=(f[i&1][j]-s[(i-1)&1][last[j]-1]+Mod)%Mod;
s[i&1][j]=(s[i&1][j-1]+f[i&1][j])%Mod;
}
ans2=(ans2+f[i&1][n]);
}
printf("%d %d\n",ans1,(ans2+Mod)%Mod);
}