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 ] < = 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]<=len) f[i][j]=k=l∑j−1f[i−1][k](其中l表示最小的l使得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题我肝了好久