【BZOJ 1044】【HAOI 2008】木棍分割【DP+优化】

Description

有n根木棍, 第i根木棍的长度为Li,n根木棍依次连结了一起, 总共有n-1个连接处. 现在允许你最多砍断m个连接处, 砍完后n根木棍被分成了很多段,要求满足总长度最大的一段长度最小, 并且输出有多少种砍的方法使得总长度最大的一段长度最小. 并将结果mod 10007。。。

Input

输入文件第一行有2个数n,m. 接下来n行每行一个正整数Li,表示第i根木棍的长度.

Output

输出有2个数, 第一个数是总长度最大的一段的长度最小值, 第二个数是有多少种砍的方法使得满足条件.

Sample Input

3 2
1
1
10

Sample Output

10 2
两种砍的方法: (1)(1)(10)和(1 1)(10)
数据范围
n<=50000, 0<=m<=min(n-1,1000).
1<=Li<=1000.

题解

【序言】这道题磨了一个上午。总算过了~~
【分析一】第一问真心的简单。直接二分枚举最大长度的最小值即可。细节注意一下。

bool check(int maxx)  
{  
  int now=0,t=1;  
  for (int i=1;i<=n;i++)  
    if (now+a[i]<=maxx) now+=a[i];  
    else   
    {  
      if (a[i]>maxx||t>=m) return false;  
      t++;now=a[i];  
    }  
  return true;  
}  
int erfen(int l,int r)  
{  
  if (l==r) return l;  
  int mid=(l+r)/2;  
  if (check(mid)) return erfen(l,mid);  
  return erfen(mid+1,r);  
}

【分析二】第二问很容易看出是DP,而且方程也很简单。设f[i][j]表示到第i个点,截取j个木棍且满足要求的方案数。为了加快速度,我们开一个前缀和的sum优化,那么i–j的长度就是sum[j]-sum[i-1]。

for (i=2;i<=n;i++)  
    for (j=2;j<=m;j++)   
      for (k=1;k<i;k++)  
        if (sum[i]-sum[k]<=ans) f[i][j]=(f[i][j]+f[k][j-1])%p; 

然后惊奇地发现:时间、空间都不符合要求!!我们来一点一点优化。
【优化一*空间】这个相对简单啊。我们通过观察发现,f[i][j]中的j只和j-1有关,所以可以用滚存。为了方便,我们可以把i,j的位置换一下,然后把j的循环提到最上面。下面的滚存代码中,now就是用来滚存的。

for (j=2;j<=m;j++)   
  {  
    now^=1;  
    memset(f[now],0,sizeof(f[now]));  
    for (i=n;i>1;i--)  
      for (k=1;k<i;k++)  
        if (sum[i]-sum[k]<=ans) f[now][i]=(f[now][i]+f[now^1][k])%p;    
    ans2=(ans2+f[now][n])%p;  
  }

其中的now^1就是0和1的变换。这样,就不用开50000*50000的数组了!空间过得去了。

【优化二*时间】想了很长时间。同样也是观察方程。每次k都要循环一边,是不是很无用啊!试想:若sum[i]-sum[k]<=ans,那么sum[i-1]-sum[k]<=ans(其中i-1>k)(因为sum数组是前缀和,满足单调递增)。因此我们可以用类似于单调队列的方法,开一个后缀数组g。如果sum[i]-sum[k]<=ans,就把计算后的值赋到g[i]中去。在以后每个小于i的数中,若i>k,i就可以加上g数组。(有点说不清楚,具体还是要自己仔细研究)

for (j=2;j<=m;j++)   
  {  
    now^=1;  
    memset(f[now],0,sizeof(f[now]));  
    memset(g,0,sizeof(g));k=n+1;  
    for (i=n;i>1;i--)  
    {  
      if (i>k) f[now][i]+=(g[k]-g[i])%p;else k=i;  
      while (k>1&&sum[i]-sum[k-1]<=ans)  
      {  
        k--;  
        f[now][i]=(f[now][i]+f[now^1][k])%p;  
        g[k]=(g[k+1]+f[now^1][k])%p;  
      }  
    }  
    ans2=(ans2+f[now][n])%p;  
  }

【小结】加了这些优化之后,bzoj上就过的去了。但是代码写的有点乱啊。真是一道经典的DP优化!

以下非转载

然后是我的代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

#define N 50010
#define mod 10007
int n,m,a[N],sum[N],f[2][N],g[N],now,ans2;

bool test(int k)
{
    int now = 0,t = 1;
    for(int i = 1;i <= n;i++)
        if(now + a[i] <= k)now += a[i];
        else
        {
            if(a[i] > k || t >= m) return false;
            t++; now = a[i];
        }
    return true;
}

int main()
{
    scanf("%d%d",&n,&m);
    m++; sum[0] = 0;
    for(int i = 1;i <= n;i++)
    {
        scanf("%d",&a[i]);
        sum[i] = sum[i-1]+a[i];
    }
    int ans,L = 0,R = sum[n];
    while(L <= R)
    {
        int mid = (L+R)>>1;
        if(test(mid)) {R = mid-1;ans = mid;}
        else L = mid+1;
    }
    printf("%d ",ans);
    for(int i = 1;i <= n;i++)
        if(sum[i] <= ans) f[1][i] = 1; else break;
    now = 1;
    for(int j = 2;j <= m;j++)
    {
        now ^= 1;
        memset(f[now],0,sizeof(f[now]));
        memset(g,0,sizeof(g));
        int k = n+1;
        for(int i = n;i > 1;i--)
        {
            if(i > k) f[now][i] += (g[k]-g[i])%mod; else k = i;
            while(k > 1 && sum[i] - sum[k-1] <= ans)
            {
                k--;
                f[now][i] = (f[now][i]+f[now^1][k]) % mod;
                g[k] = (g[k+1] + f[now^1][k]) % mod;
            }
        }
        ans2 = (ans2 + f[now][n]) % mod;
    }
    printf("%d",ans2);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值