1. 程式人生 > >【[HAOI2008]木棍分割】

【[HAOI2008]木棍分割】

沒寫過幾道的字首和優化\(dp\)

第一問是小學生難度的二分

第二問就直接\(dp\)

\(dp[i][j]\)表示當前分割點在\(i\)之後,前面一共分割了\(j\)段的方案數

利用字首和單調性,通過二分預處理出每一個點往前能擴充套件到的最大位置,之後字首和優化就可以啦

但是發現這個樣子空間會炸,而這個樣子還沒有辦法滾動起來

那好辦交換一下狀態的順序就可以啦,就可以字首和/滾動陣列優化了

程式碼

#include<iostream>
#include<cstring>
#include<cstdio>
#define re register
#define maxn 50005
#define max(a,b) ((a)>(b)?(a):(b))
const int mod=10007;
int a[maxn],n,m;
int dp[2][maxn],pre[2][maxn];
int p[maxn],to[maxn];
int ans;
inline int read()
{
    char c=getchar();
    int x=0;
    while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9')
        x=(x<<3)+(x<<1)+c-48,c=getchar();
    return x;
}
inline int check(int x)
{
    int now=0,tot=0;
    for(re int i=1;i<=n;i++)
    {
        if(now+a[i]>x) now=a[i],tot++;
        else now+=a[i];
        if(tot>m) return 0;
    }
    return tot<=m; 
}
inline int find(int l,int r,int now)
{
    int t;
    while(l<=r)
    {
        int mid=l+r>>1;
        if(p[now]-p[mid]>ans) l=mid+1;
            else r=mid-1,t=mid;
    }
    return t;
}
int l,r;
int main()
{
    n=read(),m=read();
    for(re int i=1;i<=n;i++) a[i]=read(),r+=a[i],l=max(l,a[i]);
    for(re int i=1;i<=n;i++) p[i]=p[i-1]+a[i];
    while(l<=r)
    {
        int mid=l+r>>1;
        if(check(mid)) ans=mid,r=mid-1;
            else l=mid+1;
    }
    printf("%d ",ans);
    for(re int i=1;i<=n;i++)
        to[i]=find(0,i-1,i)+1;
    for(re int i=1;i<=n;i++) if(p[i]<=ans) dp[0][i]=1;
    for(re int i=1;i<=n;i++) pre[0][i]=pre[0][i-1]+dp[0][i];
    int o=0,cnt=0;
    cnt=(cnt+dp[0][n])%mod;
    for(re int i=2;i<=m+1;i++,o^=1)
    {
        memset(dp[o^1],0,sizeof(dp[o^1]));
        for(re int j=1;j<=n;j++)
            dp[o^1][j]=(dp[o^1][j]+pre[o][j-1]-(j-2<0?0:pre[o][to[j]-2])+mod)%mod;
        pre[o^1][0]=0;
        for(re int j=1;j<=n;j++)
            pre[o^1][j]=(pre[o^1][j-1]+dp[o^1][j])%mod;
        cnt=(cnt+dp[o^1][n])%mod;
    }
    printf("%d\n",cnt);
    return 0;
}