• 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;
    }
  • 相关阅读:
    golang 查询数据库并返回json
    logrus日志使用详解
    英文文章写作的一点个人思考
    AKS (1) AKS最佳实践
    Azure Application Insight (3) ASP.NET MVC增加Application Insight
    oracle 创建存储过程后,重新编译错误,如何查看错误信息
    ORACLE 存储过程IN关键字使用问题
    Oracle/for loop循环如何进行跳过、跳出操作
    (转)Golang struct{}的几种特殊用法
    (转)Golang 延迟函数 defer 详解
  • 原文地址:https://www.cnblogs.com/ganster/p/8495937.html
Copyright © 2020-2023  润新知