题目大意:有n根木棍, 第i根木棍的长度为Li,n根木棍依次连结了一起, 总共有n-1个连接处. 可以砍最多m次,要求长度最大的一段长度最小, 并求此时的方案数
题解:第一问显示是二分+贪心检验
考虑第二问
f[i][j]表示前i个分成j块满足条件的方案数
f[i][j]=∑ki−1f[k][j−1],(sum[i]−sum[k]≤len)
这样空间是 O(nm),时间是O(n2m) ,需要优化
空间:
考虑到f[i][j]只会从f[i][j−1]转移,降维,用f[i]表示前i个分割后满足条件的方案数
同时需要预处理: if(sum[i]≤len)f[i]=1;
空间复杂度 O(n)
时间:
观察优化后的转移式
f[i]=∑ki−1f[k],(sum[i]−sum[k]≤len)
容易看出满足条件的k是一段连续区间,这提示我们可以使用前缀和加速转移
设对于i,满足条件的第一个k的值为
pre[i]
发现随着i的增大k也在增大,于是利用单调性可以轻松地
O(n)
预处理出
pre[i]
用sum[i]表示li[i]的前缀和,S表示f[i]的前缀和
f[i]=S[i−1]−S[pre[i]−1]
时间复杂度 O(nm)
我的收获:优化的思想强啊
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define P 10007
const int M=50005;
int n,m,mx,len,ans;
int li[M],sum[M];
int f[M],pre[M],S[M];
bool check(int x)
{
int cnt=0,now=0;
for(int i=1;i<=n;i++){
if(now+li[i]>x){
cnt++;
if(cnt>m) return false;
now=0;
}
now+=li[i];
}
return true;
}
void solve1()
{
for(int l=mx,r=sum[n];l<=r;){
int mid=(l+r)>>1;
if(check(mid)) len=mid,r=mid-1;
else l=mid+1;
}
}
void solve2()
{
for(int tmp=0,i=1;i<=n;i++){
while(sum[i]-sum[tmp]>len) tmp++;
pre[i]=tmp;
}
for(int i=1;i<=n&&sum[i]<=len;i++) f[i]=1;
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++) S[j]=(S[j-1]+f[j])%P;
for(int j=1;j<=n;j++) f[j]=(S[j-1]-S[pre[j]-1]+P)%P;
ans=(ans+f[n])%P;
}
ans=(ans+P)%P;
}
void work()
{
solve1();
solve2();
printf("%d %d\n",len,ans);
}
void init()
{
cin>>n>>m;
for(int i=1;i<=n;i++) scanf("%d",&li[i]),sum[i]=sum[i-1]+li[i],mx=max(mx,li[i]);
}
int main()
{
init();
work();
return 0;
}