【BZOJ1044】【tyvj3511】【codevs1870】木棍分割,二分答案+滚动数组+前缀和DP

传送门1
传送门2
传送门3
写在前面:就我看来,这是一道不错的题
思路:
一.对于“求总长度最大的一段的长度最小值”这个问题,我们比较容易想到二分答案然后判断是否合法,显然这个是可以直接贪心搞的,记录前缀和,从1-n枚举,一旦这一段长度超过mid就砍,并重新计算长度,直到某一单木棍长度超过mid或砍得超过m次,返回非法,否则合法
二.“有多少种砍的方法”这个问题着实让我纠结了很久,最后想出来一个三维DP,记录前i个木棒砍j次并且最后一个砍的地方是k,但显然时间和空间复杂度都是不能承受的,后来知道其实可以去掉k维,DP方程为
f[i][j]=Σ(f[k][j1])(sum[i]sum[k]>=minn)
就现在看,但这个二维DP的空间复杂度仍是很可怕的,如果我们用short存储(因为答案要mod10007),内存也会达到96M,同时我们要用三个循环控制变量,时间复杂度O(n*n*m),这是必定超时的,那么我们就要对这个DP进行优化
1.首先我们发现对于每一个i来说,k的最小值是固定不变的,即每次我们对f[i][j]加的即是Σ(f[p][j-1])(k<=p<=i-1),那么我们可以在对于每一个j求出一趟后维护一下区间值,争取在较短时间内调出f[i-1][j-1]到f[k][j-1],显然我们可以对每个j求一次f[1][j-1]到f[n][j-1]的前缀和,复杂度为O(n),那么我们就可以顺利在O(n)时间转移所有f[i][j](对于k的求法,我们有一个伪O(n*n)的方法,但实际效果要比n*n快很多),DP的总转移时间就由O(n*n*m)变成了O(n*m)
2.其实题目到这里就可以A了,但空间复杂度很难看,反而还加了一个存前缀和的数组,但我们在存前缀和的时候肯定不是开n*m的数组,同样我们就联想到了对于该题的DP方程我们同样不必开n*m的数组,由于每次转移只用到j-1的状态,所以我们可以改成滚动数组,同样前缀和也是,至此,题目完成
注意:
1.初始化时,我们对所有前缀和小于等于二分出的答案的f[i][0]=1,其他为0
2.转移时f[i][x]一定是赋值而不是+=,因为开的是滚动数组
3.小心越界,一生平安

#include<bits/stdc++.h>
#define mod 10007
using namespace std;
int n,m,minn,ans,x;
int f[50010][2],g[50010][2],pos[50010],len[50010],sum[50010];
inline bool check(int x)
{
    int k=0,tot=0;
    for (int i=1;i<=n;i++)
    if (sum[i]-sum[k]>x)
    {
        if (len[i]>x||tot==m) return 0;
        tot++;
        k=i-1;
    }
    return 1;
}
main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
    scanf("%d",&len[i]),
    sum[i]=sum[i-1]+len[i];
    int l=1,r=sum[n],mid=(l+r)>>1;
    while (l<=r)
    {
        if (check(mid)) minn=mid,r=mid-1;
        else l=mid+1;
        mid=(l+r)>>1;
    }
    for (int i=1;i<=n;i++)
    if (sum[i]<=minn) f[i][0]=1;
    else break;
    for (int i=1;i<=n;i++)
    {
        if (sum[i]<=minn) continue;
        for (int j=i-1;j>=0;j--)
        if (sum[i]-sum[j]>minn) {pos[i]=j+1;break;}
    }
    while (m--)
    {
        for (int i=1;i<=n;i++)
        g[i][x]=g[i-1][x]+f[i][x];
        x^=1;
        for (int i=1;i<=n;i++)
        f[i][x]=(g[i-1][!x]-g[max(pos[i]-1,0)][!x])%mod;
        ans=(ans+f[n][x])%mod;
    }
    printf("%d %d",minn,ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值