luogu P2511 [HAOI2008]木棍分割

luogu P2511 [HAOI2008]木棍分割

大意

比较清楚,就是把一个序列分成m+1段。
第一问:求最大那段最小是多少?
第二问:在满足最大那段最小的情况下有几种分法 % 10007

题解

首先第一问是道入门二分答案 (我才不会告诉你我一开始写错了)
假设第一问的长度为len
则第二问是道pj DP
f [ i ] [ j ] f[i][j] f[i][j]表示,前i个物品,分成j段且满足第一问的方案数
可得

f [ i ] [ j ] = ∑ k = l j − 1 f [ i − 1 ] [ k ] ( 其 中 l 表 示 最 小 的 l 使 得 a [ l + 1 ] + a [ l + 2 ] + . . . . + a [ j ] &lt; = l e n ) f[i][j] = \sum\limits_{k=l}^{j-1} f[i-1][k](其中l表示最小的l使得a[l+1]+a[l+2]+....+a[j]&lt;=len) f[i][j]=k=lj1f[i1][k]ll使a[l+1]+a[l+2]+....+a[j]<=len)

然后发现f可以滚掉一维,然后这个东东还可以用前缀和优化


// luogu-judger-enable-o2
#include<bits/stdc++.h>
#define mod 10007
#define N 50005
using namespace std;
int n, m, a[N], len, f[N], g[N], sum[N];
int check(int x){
	int ret = 0, s = 0;
	for(int i = 1; i <= n; i ++){
		if(a[i] > x) return 0;
		if(s + a[i] <= x) s += a[i];
		else ret ++, s = a[i];
	}
	if(s) ret ++;
	return ret <= m;
}
int main(){
	scanf("%d%d", &n, &m); m ++;
	for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
	int l = 0, r = 1000000000;
	while(l + 1 < r){
		int mid = (l + r) >> 1;
		if(check(mid)) r = mid;
		else l = mid;
	}
	len = r;//先二分答案出第一问的答案
	for(int i = 1; i <= n; i ++) sum[i] = sum[i - 1] + a[i];
	for(int i = 0; i <= n; i ++) g[i] = 1;
	int ret = f[n];
	for(int i = 1; i <= m; i ++){
		int pos = 0;
		for(int j = 1; j <= n; j ++){
			while(pos <= j && sum[j] - sum[pos] > len) pos ++;//找l
			f[j] = (g[j - 1] - (pos? g[pos - 1]:0) + mod) % mod;//转移
		}
		g[0] = 0;
		for(int j = 1; j <= n; j ++) g[j] = (g[j - 1] + f[j]) % mod;//g为上一维的前缀和,方便转移
		ret += f[n], ret %= mod;
	}
	printf("%d %d", len, ret);
	return 0;
} 

以后不要爆肝,效率非常低
像这种zz题我肝了好久

posted @ 2019-08-23 08:52  lahlah  阅读(35)  评论(0编辑  收藏  举报