【算法•日更•第七期】区间动态规划详解+一本通1570能量项链题解

▎前置基础

  啥也不用说,不会动态规划绝对看不懂,请不会动态规划的同志们先戳这里了解基础动态规划

▎什么是区间动态规划?

  区间动态规划可以理解为用了分治的动态规划。

  顾名思义,动态规划中涉及了区间,那么这正好和分治常用题型一样,如果你知道分治是什么,那么就很容易理解了。

  那么先来想想分治是什么?分而治之,就是把一个大问题分成小问题,再把小问题的结果合并起来,就得到了原大问题的结果。

  掺了分治的动态规划也可以这样,比方说现在要求f[i][j],那么我们总能找到k,使i≤k<j,分别求出f[i][k]f[k+1][j]的值,再加起来,就是f[i][j]的值。图解一下就是这样的:

  

  当要求的是最优值时就可以表示为f[i][j]=max{f[i][k]+f[k+1][j]},其中k是变量,依次在区间[i,j]中找到k值,计算找到最大的f[i][k]+f[k+1][j]值,就是f[i][j]的值。

  你可能会问怎么知道f[i][k]和f[k+1][j]的值,很简单,要么递归,要么递推,递归的话直到递归边界会返回一个值,逐步回带;递推是一步一步算上来的,在算f[i][j]以前就先算过了f[i][k]和f[k+1][j]。

▎实战演练

   可能有点难理解,主要是却例子,那么就直接上题吧!


 

信息奥赛一本通1570能量项链题

『题目』

1570:【例 2】能量项链


时间限制: 1000 ms         内存限制: 524288 KB
提交数: 279     通过数: 189 

【题目描述】

原题来自:NOIP 2006

在 Mars 星球上,每个 Mars 人都随身佩带着一串能量项链。在项链上有 N 颗能量珠。能量珠是一颗有头标记和尾标记的珠子,这些标记对应着某个正整数。并且,对于相邻的两颗珠子,前一颗珠子的尾标记必定等于后一颗珠子的头标记。因为只有这样,通过吸盘——Mars 人吸收能量的器官的作用,这两颗珠子才能聚合成一颗珠子,同时释放出可被吸盘吸收的能量。如果一颗能量珠头标记为 m,尾标记为 r,后一颗能量珠头标记为 r,尾标记为 n,则聚合后释放出 m×r×n  Mars单位的能量,新珠子头标记为 m,尾标记为 n

当需要时,Mars 人就用吸盘夹住相邻的两颗珠子,通过聚合得到能量,直到项链上只剩下一颗珠子为止。显然,不同的聚合顺序得到的总能量是不一样的。请设计一个聚合顺序使得一串珠子聚合后释放出的总能量最大。

例如,设 N=4,四颗珠子头标记与尾标记分别为 (2,3),(3,5),(5,10),(10,2)。我们用记号  表示两颗珠子的聚合操作,(jk) 表示 j,k两颗珠子聚合后释放出的能量,则4,1两颗珠子聚合后所释放的能量为(41)=10×2×3=60,这一串项链可以得到最优值的一个聚合顺序所释放出的总能量为(((41)2)3)=10×2×3+10×3×5+10×5×10=710

现在给你一串项链,项链上有 n 颗珠子,相邻两颗珠子可以合并成一个,合并同时会放出一定的能量,不同珠子合并放出能量不相同,请问按怎样的次序合并才能使得释放的能量最多?

【输入】

第一行一个正整数 n

第二行 n 个不超过 1000 的正整数,第 i(1in) 个数为第 i 颗珠子的头标记,当 in 时第 i 颗珠子的尾标记等于第 i+1 颗珠子的头标记,当 i=n 时第 i 颗珠子的尾标记等于第 1 颗珠子的头标记。

至于珠子的顺序,你可以这样确定:将项链放在桌面上,不要出现交叉,随机指定一颗珠子为第一颗珠子,按顺时针确定其它珠子的顺序。

【输出】

输出只有一行,一个不超过 2.1×109 的正整数,表示最优聚合顺序所释放的能量。

【输入样例】

4
2 3 5 10

【输出样例】

710

【提示】

数据范围与提示:

对于 100% 的数据,4n100

【来源】


『题目分析』

  这道题一看似乎用原来的动态规划不好解决,那么一看到要合并,又会用到区间,就自然而然的想到了区间动态规划。

  首先就有一个问题,这是个环状问题,十分棘手,遇到这种情况,我们就应该破环成链,那么怎么样才会成链呢?很简单,只要变成两个就行了。可能没有说明白,举个例子。比如说数据是这样的:1 2 3 4 5,那么我们就改成这样1 2 3 4 5 1 2 3 4 5,似乎没有什么,请先仔细看下图:

  

  左边的环状正是以任一数作为第一个都可以。而右边呢?也可以!比如说3开头,那么请看:

  

  细细一看,这就保留了环的性质,唯一要注意的就是操作时不要忘记这是5个数,而不是10个数。

  好了,回到这道题,我们可以使用破环成链的方法使问题更简便。

  那么我们如何区间动态规划呢?

  首先先来思考,我们一定是先合并小的区间,一步一步地合到大区间,而不是直接上来合并大区间的,小区间还没算,大区间怎么可能算过?

  所以我们肯定是先合并相邻的,再合并区间长为2的,再是3的,4的……所以,我们需要一个变量t来存储我们现在需要合并长为几的区间,同时根据这个变量t和起始位置i来确定j的位置。

  这样,思路就很清晰了,详见代码注释。

Code speaks louder than words!

 1 #include<iostream>
 2 using namespace std;
 3 int head[10000],tail[10000],f[1000][1000],num,ans;
 4 inline int add(int n,int m,int r)//计算获得的能量值 
 5 {
 6     return n*m*r;
 7 }
 8 int main()
 9 {
10     cin>>num;
11     for(int i=1;i<=num;i++)
12     {
13         cin>>head[i];
14         head[i+num]=head[i];//破环成链 
15     }
16     for(int i=1;i<=2*num-1;i++)
17     {
18         tail[i]=head[i+1];//计录尾标记 
19     }
20     tail[2*num]=head[1];//最后一个是特例,要单独赋值 
21     for(int t=1;t<=num-1;t++)//合并区间的长度 
22     for(int i=1;i<=2*num-t;i++)//剪t是为了避免j超出2*num 
23     {
24         int j=i+t;
25         for(int k=i;k<j;k++)//i,j之间的过继点 
26         {
27             f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]+add(head[i],tail[k],tail[j]));//详见上文 
28         }
29     }
30     for(int i=1;i<=num;i++) ans=max(ans,f[i][i+num-1]);//因为每一个点都可以是第一个,找到所有情况中的最大值 
31     cout<<ans;
32     return 0;
33 }
posted @ 2019-07-10 15:26  c1714-gzr  阅读(619)  评论(0编辑  收藏  举报