suxxsfe

一言(ヒトコト)

[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;
}
posted @ 2020-06-08 22:16  suxxsfe  阅读(171)  评论(0编辑  收藏  举报