动态规划训练之十四
https://www.luogu.org/problem/P2511
首先分析
本题有两个问,而这两个问的求法肯定是不一样的
第一问,求最长的最短,很显然一个二分就行
那第二问计数怎么办?
f[i,j]代表前i个数分成j块的方案数,
则f[i,j]=Σ f[k,j-1] (k>=left[i]&&k<i) ,而因为空间问题,是不可以开1000*50000个数组的,
而本题正好要用到前缀和,甚至连滚动数组都不用开,直接一个数组f记录当前j的所有i的答案,
而s记录j-1的所有i的答案的前缀和。
每次求完本轮之后再更新s。
这是一道很好的动规优化
时间复杂度(N*M)
看了程序之后再解析
code:
#include<bits/stdc++.h>
#define ll int
using namespace std;
inline void read(ll&x){
x=0;char ch=getchar();
while(!isdigit(ch)) ch=getchar();
for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';
}
const ll ha=10007;
ll n,m,l,r,mid,ans;
ll a[50005],s[50005],f[50005],tmp=0;
ll lef[50005],now;
inline bool work(ll x){
ll k=0,t=1;
for(ll i=1;i<=n;i++)if(a[i]>x) return false;
else if(k+a[i]>x){
t++,k=a[i];
if(t>m) return false;
} else k+=a[i];
return true;
}
int main(){
read(n),read(m);
m++;
for(ll i=1;i<=n;i++) read(a[i]);
l=1,r=50000000;
while(l<=r){
mid=l+r>>1;
if(work(mid)) ans=mid,r=mid-1;
else l=mid+1;
}
cout<<ans<<' ';
now=0;
for(ll i=1;i<=n;i++){
a[i]+=a[i-1];
while(a[i]-a[now]>ans) now++;
lef[i]=now;
}
memset(f,0,sizeof(f));
fill(s,s+n+1,1);
for(ll i=1;i<=m;i++){
for(ll j=1;j<=n;j++) f[j]=(s[j-1]-s[lef[j]-1])%ha;
s[0]=0;
for(ll j=1;j<=n;j++) s[j]=f[j]+s[j-1];
tmp=(tmp+f[n])%ha;
}
cout<<tmp;
return 0;
}
为什么可以这样?(直接减少了一维)
因为所有的f[i,j]只是从f[k,j-1]转移过来,这里的第二维始终是j-1
当然滚动数组是必然可行的