[HAOI2008] 木棍分割
吐槽环节
- 为什么一分部分分不给啊,一开始写了个
n3,居然直接爆蛋。
- 前缀和优化
DP还要滚存
题目意思
Sol
- 前置知识:前缀和优化DP
- 对于第一问就是个
hh问题,直接二分就可以了。这个没什么好讲,看看代码:
inline bool check(int mid)
{
int sum=0,all=0;
for ( int i=1;i<=n;i++ )
{
if(a[i]>mid) return false;
if(all+a[i]>mid)
all=a[i],sum++;
else all+=a[i];
}
return (sum<=m);
}
- 对于第二问我们可以用
DP来解决。首先我们会显而易见的想到
fi,j表示到第
i根木棒,分成
j组的方案数。然后就是直接
n3做:
fi,j=∑k=1i−1fk,j−1[sum(i)−sum(k)≤x]。我们再来看看
n3的代码(不要以为你会有分,就是爆蛋)
inline int n_three_dp(int x)
{
for ( int i=1;i<=n;i++ )
if(sum[i]<=x) f[i][0]=1;
for ( int j=1;j<=m;j++ )
for ( int i=1;i<=n;i++ )
for ( int k=0;k<i;k++ )
if(sum[i]-sum[k]<=x)
Add(f[i][j],f[k][j-1]);
int ans=0;
for ( int i=0;i<=m;i++ ) Add(ans,f[n][i]);
return ans;
}
- 到现在我们还是
0分,我们考虑用前缀和优化。
prei,j=∑k=1jfi,k,然后我们用前缀和去更新
fi,j,就是
fi,j=prei−1,j−prei−1,k−1。然后我们再交一下,发现还是
0分(qwq…
- 那我们仔细观察这个转移发现对于一个固定的
j我们去枚举若干个
k会浪费时间。于是我们记录第一个
k满足
sumi−sumk≤x的位置为
Pi。这里有点像容斥(就是总方案数减去不合法方案数)。
- 对于每个
Pi我们考虑到
sum的单调性,不需要
O(n2)计算。看看代码
for ( int i=1;i<=n;i++ )
for ( ;j<i;j++ )
if(sum[i]-sum[j]<=x)
{
ed[i]=j;
break;
}
- 这样我们时空都是
O(n×m),空间不行,考虑滚存(后面就是简单套路了,
f[],pre[]互相更新即可。
- 具体看代码,这样我们就成功把空间降到
O(n),能过
Code
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#include <bits/stdc++.h>
#define pb push_back
using namespace std;
inline int read()
{
int sum=0,ff=1; char ch=getchar();
while(!isdigit(ch))
{
if(ch=='-') ff=-1;
ch=getchar();
}
while(isdigit(ch))
sum=sum*10+(ch^48),ch=getchar();
return sum*ff;
}
const int N=5e4+5;
const int mo=10007;
int n,m,ans,a[N],sum[N],f[105][1005],g[N],pre[N],ed[N],ret;
inline bool check(int mid,int &s)
{
int sum=0,all=0;
for ( int i=1;i<=n;i++ )
{
if(a[i]>mid) return false;
if(all+a[i]>mid)
all=a[i],sum++;
else all+=a[i];
}
s=sum+(all!=0);
return (sum<=m);
}
inline void Add(int &x,int y)
{
x+=y;
if(x>=mo) x%=mo;
}
inline int n_three_dp(int x)
{
for ( int i=1;i<=n;i++ )
if(sum[i]<=x) f[i][0]=1;
for ( int j=1;j<=m;j++ )
for ( int i=1;i<=n;i++ )
for ( int k=0;k<i;k++ )
if(sum[i]-sum[k]<=x)
Add(f[i][j],f[k][j-1]);
int ans=0;
for ( int i=0;i<=m;i++ ) Add(ans,f[n][i]);
return ans;
}
inline int dp(int x)
{
for ( int i=1;i<=n;i++ )
{
if(sum[i]<=x) g[i]=1;
pre[i]=pre[i-1]+g[i];
}
int j=0;
for ( int i=1;i<=n;i++ )
for ( ;j<i;j++ ) if(sum[i]-sum[j]<=x)
{
ed[i]=j;
break;
}
for ( int j=2;j<=m+1;j++ )
{
for ( int i=1;i<=n;i++ )
{
g[i]=pre[i-1];
if(ed[i]>=1) g[i]=(g[i]-pre[ed[i]-1]+mo)%mo;
}
pre[0]=0;
for ( int i=1;i<=n;i++ )
pre[i]=(pre[i-1]+g[i])%mo;
Add(ret,g[n]);
}
ret+=(sum[n]<=x);
return (ret+mo)%mo;
}
signed main()
{
n=read();
m=read();
int mx=0;
for ( int i=1;i<=n;i++ )
{
a[i]=read();
mx=max(mx,a[i]);
sum[i]=sum[i-1]+a[i];
}
int l=mx,r=sum[n];
ret=0;
while(l<=r)
{
int mid=(l+r)/2;
int s=0;
if(check(mid,s))
ans=mid,r=mid-1;
else l=mid+1;
}
if(n<=100)
printf("%d %d\n",ans,n_three_dp(ans));
else
printf("%d %d\n",ans,dp(ans));
return 0;
}