cut

Problem . cut

Background:

        伐木工Zxr非常懒惰了,所以他在伐木的时候只会找准木头被虫蛀过的腐朽的地方砍下去。

Description:

        一块有N-1处被虫蛀过的地方,假设被虫蛀过的地方长度不计。这n-1个虫蛀将木头分成了n块,题目将依次给出这n块木头的长度。懒惰的zxr最多只想砍m次,并且希望可以借此把这块木头分得尽量均匀,即希望使砍伐后连续的木块中最长的尽量短。这个时候聪明的你跳了出来“我不仅能算出这个最短长度,我还能算出方案数!”

Input:

            第一行两个数n,m。

            接下来一行n个数分别表示这N块木头的长度。

Output:

            一行两个数分别表示最短的最长长度和方案数。

Sampleinput:

            32

            1110

Sampleoutput:

            102

Hint:

            两种砍的方法: (1)(1)(10)和(1 1)(10)。

            对于30%的数据,n<=10,

            对于100%的数据,n<=50000,

0<=m<=min(n-1,1000),1<=Li<=1000.

题解 :

       首先这道题一共有两个问题,第一问求最长长度的最短值,很明显用二分,只需要每次二分的时候check()一下,然后我们就可以得到这个长度len。

       然后来分析第二个问题,第二个问题求方案数,一共有n块,最多切m刀,所以dp数组中需要包含着两个元素,所以我们就设我们的dp数组dp[ i ][ j ]表示:

       前i段切了j刀得到的方案数;

状态转移方程:

dp[ i ][ j ] = sigma( dp[ k ][ j - 1 ] ) ( 1 <= k <= i - 1 ),我们在设一个前缀数组sum[ i ],所以k还需要满足sum[ i ] - sum[ k ] <= len。

       我们再来看一下数据范围,n是50000,而m是1000,所以我们的空间复杂度和时间复杂度都超限制了,所以需要优化:

优化空间:

       我们发现每一个状态j都只与j - 1有关,所以我们可以设一个now,表示当前状态即j,再设一个last表示j - 1,last = now ^ 1,这样就将空间复杂度优化了。

优化时间:

       我们来想一下,我们每次更新dp[ i ][ j ],都需要用到前面的dp[ k ][ last ]的和,所以我们不妨用一个sumf来记录这个和,这就省去了循环k的时间,但是我们还需要满足sum[ i ] - sum[ k ] <= len,所以我们sumf中还需要减去一些不需要的k,我们不难发现sum[ i ]递增的数组,所以我们需要的满足sum[ i ] - sum[ k ] <= len的最小k值mink的值也不会是递减的,所以我们只需要每次对于i,mink的值向后推移,同时在sumf中将dp[ mink ][ last ]的值减去就可以了,这样我们的时间复杂度就优化为O( mn )。

代码:

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

const int N = 50001, M = 1001, mod = 10007;
int dp[N][2], sum[N], n, m, len, tot, a[N];

bool check(int val){
  int res = 0, g = 0;
  for (int i = 1; i <= n; i++){
      if (res + a[i] <= val){
        res += a[i]; continue;
      } else {
        if (a[i] > val) return false;
        g++;
        if (g > m) return false;
        res = a[i];
      }
  }
  return true;
}

void minlen(){//二分求len
  int ll = 1, rr = tot;
  while (ll != rr){
      int mid = (ll + rr) >> 1;
      if (check(mid)) rr = mid;
      else ll = mid + 1;
  }
  len = rr;//必须是len = rr,不是len = mid或者len = ll!
}

int main(){
  freopen("cut.in", "r", stdin);
  freopen("cut.out", "w", stdout);
  scanf("%d%d", &n, &m);
  for (int i = 1; i <= n; i++){
      scanf("%d", &a[i]);
      tot += a[i];
      sum[i] = sum[i - 1] + a[i];
  }
  minlen();
  int now = 0, last = 1, ans = 0,sumf, mink;
  for (int j = 0; j <= m; j++){
      sumf = 0;
      mink = 1;
      for (int i = 1; i <= n; i++){
        if (j == 0){
            if (sum[i] <= len) dp[i][now] = 1;
            else dp[i][now] = 0;
        } else {
            while (mink < i && sum[i] - sum[mink] > len){
              sumf -= dp[mink][last];
              sumf = (sumf + mod) % mod;//模一个mod是因为有可能sumf会减成负数
              mink++;
            }
            dp[i][now] = sumf;
        }
        sumf += dp[i][last];
        sumf %= mod;
      }
      ans += dp[n][now];
      ans %= mod;
      now ^= 1;
      last = now ^ 1;
  }
  printf("%d %d", len, ans);
  return 0;
}
原文地址:https://www.cnblogs.com/ganster/p/8495937.html