二分 DP
第一问二分后贪心即可。第二问要DP。
令 f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i个分成 j j j段的方案数。那么有 f [ i ] [ j ] = ∑ f [ k ] [ j − 1 ] ( ∑ p = k p < i a [ p ] ≤ a n s 1 ) f[i][j]=\sum f[k][j-1](\sum_{p=k}^{p<i} a[p]\leq ans1) f[i][j]=∑f[k][j−1](∑p=kp<ia[p]≤ans1),其中 a n s 1 ans1 ans1为第一问答案.
首先这个东西只和 j j j的前一维有关,那么可以滚掉或直接砍掉一维。而 i i i这一维又是一个前缀和的性质,且 k k k是单调往右的。于是可以预处理 l [ i ] = k i l[i]=k_i l[i]=ki或者每次推一遍。设 S [ i ] = ∑ f [ i ] S[i]=\sum f[i] S[i]=∑f[i],那么 f [ i ] = S [ i − 1 ] − S [ l [ i ] − 1 ] f[i]=S[i-1]-S[l[i]-1] f[i]=S[i−1]−S[l[i]−1]。
代码:
#include<cctype>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 50005
#define F inline
using namespace std;
const int p=10007;
int n,m,ans1,ans2,f[N],g[N],s[N],a[N],l[N];
F char readc(){
static char buf[100000],*l=buf,*r=buf;
if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
return l==r?EOF:*l++;
}
F int _read(){
int x=0; char ch=readc();
while (!isdigit(ch)) ch=readc();
while (isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=readc();
return x;
}
F bool pd(int p){
int s=0,S=0;
for (int i=1;i<=n;i++) S+a[i]>p?s++,S=a[i]:S+=a[i];
return s<=m;
}
F void dp(){
for (int now=0,i=1;i<=n;i++){
while (s[i]-s[now]>ans1) now++; l[i]=now;
}
for (int i=1;i<=n&&s[i]<=ans1;i++) f[i]=1;
for (int j=1;j<=m;j++){
for (int i=1;i<=n;i++) g[i]=(g[i-1]+f[i])%p;
for (int i=1;i<=n;i++) f[i]=(g[i-1]-g[l[i]-1]+p)%p;
(ans2+=f[n])%=p;
}
}
int main(){
n=_read(),m=_read(); int l=0,r,mid;
for (int i=1;i<=n;i++) l=max(l,a[i]=_read()),s[i]=s[i-1]+a[i];
for (r=s[n];l<=r;) pd(mid=l+r>>1)?ans1=mid,r=mid-1:l=mid+1;
return dp(),printf("%d %d\n",ans1,ans2),0;
}