zoukankan      html  css  js  c++  java
  • 石子合并(NOI1995)题解

    题目描述

    在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。

    试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分.

    输入输出格式

    输入格式:

    数据的第1行试正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数.

    输出格式:

    输出共2行,第1行为最小得分,第2行为最大得分.


    1995的NOI题目,然而却是一道非常水的区间DP。

    区间DP,顾名思义,求区间最值问题。通过小区间来更新大区间,最后逐渐更新出答案。

    区间DP常用枚举套路:

    for(int len = 2;len<=n;len++)
    {
        for(int i = 1;i+len-1<=n;i++)
        {
            int j = i+len-1;
            dp[i][j] = ...
        }
    }

    外层枚举长度,下一层枚举初始端点,终点通过长度+起点-1枚举出来,需要注意的是起点枚举范围是i+len-1,也就是终点要在区间长度以内。

    继续说这道题目。

    大区间一定是通过小区间合并出来的,这也是我们使用区间DP的原因。但是这道题并不是一条链上的石子,而是一个环。也就是说我们有可能在最后一个石子回头,与前面的石子合并。

    那我们只需要把原来的链的长度变成二倍(除了最后一位),而枚举长度仍然是一倍的长度不就好了?

    举个例子:

      2,3,4,5

    我们可以把它变成2,3,4,5,2,3,4

    枚举的时候仍然是4的长度。这样就完美的处理了链的情况。

    接下来是状态转移方程。

    不知道有没有同学会和我开始时候有一样的错觉,全部合并到一起不就是所有的值相加吗?

    然而并不是这样的,当一个区间与另一个区间合并时,原来的区间的数被算了两次。

    再举个栗子。

    [1,2] 与[2,3]合并

    前者合并之后是3,后者是5

    这样在合并一次就是3+5+8

    相当于1+2+2+3+1+2+2+3

    虽然这个结论是错的,但是我们从中可以得到一个结论,每次合并的时候,区间内的所有值都会被再次算一遍。

    所以要预处理前缀和。

    接下来说方程。

    dp[i][j]表示把[i][j]中的石子合并成一堆所需要的费用。

    [i][j]之间我们可以选择任意一个点,把这个区间分成两段,通过这两段合并成这一段区间。

    所以

    dpmax[i][j] = max(dpmax[i][j],dpmax[i][k-1]+dpmax[k][j]+before[j]-before[i-1]);

    dpmin[i][j] = min(dpmin[i][j],dpmin[i][k-1]+dpmin[k][j]+before[j]-before[i-1]); 

    before代表前缀和

    每个点的初始值就是它自己的花费。

    最后上代码。

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<string>
    using namespace std;
    int num[202];
    int before[202];
    int dpmax[202][202];
    int dpmin[202][202];
    int main()
    {
          int n;
          scanf("%d",&n);
          for(int i = 1;i<=n;i++)
          {
                scanf("%d",&num[i]);
                num[n+i] = num[i];
                before[i] = before[i-1]+num[i];
          }
          for(int i = n+1;i<=2*n-1;i++)
          {
                before[i] = before[i-1]+num[i];
          }
          memset(dpmin,0x3f,sizeof(dpmin));
          memset(dpmax,-1,sizeof(dpmax));
          for(int i = 1;i<=2*n-1;i++)
          {
                dpmin[i][i] = 0;
                dpmax[i][i] = 0;
          }
          for(int len = 2;len<=n;len++)
          {
                for(int i = 1;i+len-1<=2*n-1;i++)
                {
                      int j = i+len-1;
                      for(int k = i+1;k<=j;k++)
                      {
                            dpmax[i][j] = max(dpmax[i][j],dpmax[i][k-1]+dpmax[k][j]+before[j]-before[i-1]); 
                            dpmin[i][j] = min(dpmin[i][j],dpmin[i][k-1]+dpmin[k][j]+before[j]-before[i-1]);       
                      }
                }
          }
          int ma = -1;
          int mi = 2147483467;
          for(int i = 1;i<=n;i++)
          {
                ma = max(ma,dpmax[i][i+n-1]);
                mi = min(mi,dpmin[i][i+n-1]);
          }
          printf("%d\n%d",mi,ma);
          return 0;
    }
    /*
    3
    1 2 3
    */
  • 相关阅读:
    http请求
    mac chrome NET::ERR_CERT_INVALID
    百度小程序获取用户手机号
    js 异步总结
    百度小程序 es6 支持情况
    每日日报
    每日日报
    每日日报
    05程序员修炼之道:从小工到专家阅读笔记之二
    05程序员修炼之道:从小工到专家阅读笔记之一
  • 原文地址:https://www.cnblogs.com/lizitong/p/10014809.html
Copyright © 2011-2022 走看看