[bzoj1044] P2511 [HAOI2008]木棍分割

https://www.luogu.com.cn/problem/P2511
https://darkbzoj.tk/problem/1044

dp+前缀和优化
人傻常数大,最后一个点跑了0.8秒/jk

第一问,直接二分,然后贪心来判断是否可行就好,记求出的答案(最大长度最小值)为 (len)
我一菜鸡在这一问就调了好长时间

预处理出一个 (p_i),表示的是,能与 (i) 构成一个区间的最左端的一个点是什么
比如 (p_i=j),也就是 ([j,i]) 区间是符合要求的。([j-1,i]) 就不是了,也就是它比 (len)
这个也可以简单的用二分求出

然后开始dp,设 (f_{i,j}) 为前 (i) 个木棍,恰好分成 (j) (不是切 (j) 刀),有多少方案
至于为什么是恰好这位大佬讲的挺详细了
那么,(f_{i,j})(f_{k,j-1}) 推出,也就是考虑给已经分成 (j-1) 段的每个情况,再加上一些木棍,更具体就是:

[f_{i,j}=sum_{k=p_i-1}^{i-1} f_{k,j-1} ]

也就是从 (p_i-1) 开始,可以为之前的状态加上 ([p_i,i]) 的木棍,来推出当前状态,直到枚举上限,就是加区间 ([i,i]) 的木棍
发现这样转移是 (n^3) 的,但是由于是区间求和的方式来转移,所以用一下前缀和优化
维护一个 (sum)
每次把 (f_{i-1,j-1}) 加入 (sum) 中,然后从 (sum) 中把所有 (f_{x,j-1},x<p_i-1) 减去
所以当前的 (f_{i,j}) 就等于 (sum) 了,复杂度 (n^2)
前缀和优化写的不熟,处理起边界来真的烦

然后数组要用滚动数组,答案是 (sum_{i=1}^{len} f_{n,i})

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<map>
#include<iomanip>
#include<cstring>
#define reg register
#define EN std::puts("")
#define LL long long
inline int read(){
	register int x=0;register int y=1;
	register char c=std::getchar();
	while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
	while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
	return y?x:-x;
}
#define mod 10007
int a[50005];
int n,m,len;
int f[50005][2];//f[i][j] 前 i 个,分成 j 段,并符合最大长度的限制,的方案数 
int sum[50005],p[50005];
inline int check(int max){
	for(reg int i=1;i<=n;i++)if(a[i]>max) return 0;
	for(reg int num=0,now,i=1;i<=n;){
		now=0;
		while(now<=max&&i<=n) now+=a[i],i++;
		if(now>max) i--;
		if(++num>m) return 0;
	}
	return 1;
}
inline int get(){
	reg int l=1,r=sum[n],mid,ret;
	while(l<=r){
		mid=(l+r)>>1;
		if(check(mid)) r=mid-1,ret=mid;
		else l=mid+1;
	}
	return ret;
}
inline int pre(int x){
	reg int l=1,r=x,ret,mid;
	while(l<=r){
		mid=(l+r)>>1;
		if(sum[x]-sum[mid-1]<=len) ret=mid,r=mid-1;
		else l=mid+1;
	}
	return ret;
}
inline int dp(){
	for(reg int i=1;i<=n;i++) p[i]=pre(i);
	for(reg int i=1;i<=n;i++)if(sum[i]<=len) f[i][1]=1;
	int ans=f[n][1];
	for(reg int tail,sum,j=2;j<=m;j++){
		tail=p[2]-1;sum=f[p[2]-1][(j-1)&1];
		for(reg int i=2;i<=n;i++){
			sum=(sum+f[i-1][(j-1)&1])%mod;
			while(tail<p[i]-1) sum=(sum-f[tail++][(j-1)&1]+mod)%mod;
			f[i][j&1]=sum;
		}
		ans=(ans+f[n][j&1])%mod;
	}
	return ans;
}
int main(){
	n=read();m=read()+1;
	for(reg int i=1;i<=n;i++) a[i]=read(),sum[i]=sum[i-1]+a[i];
	len=get();
	std::printf("%d ",len);
	std::printf("%d",dp());
	return 0;
}