NKOJ4244 HAOI2008 木棍分割
问题描述
有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.
这道题的综合性比较强。考察了很多知识点,尤其是对于优化的考察。但是其实并没有太复杂。
第一个问题,“长度最大的最小”,显然的二分答案标志。轻松水过。
第二个问题,考虑递推。定义状态 f[i][j] 表示前i根木棍切j刀,且第j刀切在i处的方案总数,那么有
所以我们需要优化。滚动数组的优化是很容易想到的,因为注意到 f[i][j] 只与第二维是 j−1 的状态有关系。
递推式里面有多个连续的数求和的形式,考虑前缀和优化。但是这里有个问题:如何定位
k
?注意到
优化之后,时间复杂度 O(mn) ,空间复杂度 O(2m) ,就能够AC了。
代码:
#include<stdio.h>
using namespace std;
const int mod=10007,MAXN=50005;
int N,M,Ans,sum[MAXN],Max,f[MAXN],sumf[MAXN][2];
int Q[MAXN],head,tail;
inline int _R()
{
char s=getchar();int v=0,sign=0;
while((s!='-')&&(s>57||s<48))s=getchar();
if(s=='-')sign=1,s=getchar();
for(;s>47&&s<58;s=getchar())v=v*10+s-48;
if(sign)v=-v;
return v;
}
bool check(int x)
{
int cnt=0,i,las=0;
for(i=1;i<=N;i++)
{
if(sum[i]-sum[las]>x)
{
cnt++;
las=i-1;
}
if(cnt>M)return false;
}
return true;
}
int main()
{
int L,R=0,mid,i,j,x,y,t;
N=_R();M=_R();
for(i=1;i<=N;i++)
{
x=_R(),sum[i]=sum[i-1]+x;
Max=Max<x?x:Max;
}
L=Max;R=sum[N];
while(L<=R)
{
mid=L+R>>1;
if(check(mid))R=mid-1;
else L=mid+1;
}
printf("%d ",L);
for(i=1;i<=N;i++)
{
if(sum[i]<=L)f[i]=1;
sumf[i][1]=sumf[i-1][1]+f[i];
}
Ans+=f[N];
for(j=1;j<=M;j++)
{
head=0;
x=j&1;y=x^1;
for(i=1;i<=N;i++)
{
while(sum[i]-sum[head]>L)head++;
t=head;
if(t)t--;
f[i]=(sumf[i-1][x]-sumf[t][x])%mod;
sumf[i][y]=(sumf[i-1][y]+f[i])%mod;
}
Ans=(Ans+f[N])%mod;
}
printf("%d",(Ans+mod)%mod);
}