动态规划训练之十四

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

当然滚动数组是必然可行的

原文地址:https://www.cnblogs.com/wzxbeliever/p/11704292.html