有N个标有值Bi的气球(其中B(i…N))。
用户将获得带有N枚子弹的枪,并且用户必须射击N次。
当任何气球爆炸时,其相邻的气球会彼此相邻。
用户必须得分最高才能获得奖金,并且得分从0开始。
以下是计算分数的条件。
当气球Bi爆炸时,得分将为Bi-1和Bi + 1的乘积(分数= Bi-1 * Bi + 1)。
当气球Bi爆炸并且只剩下一个气球时,得分将为Bi-1。
当气球Bi爆炸并且只有正确的气球存在时,得分将为Bi + 1。
当气球Bi爆炸并且不存在左右气球时,得分将为Bi。
编写程序以得分最高分。
Example:
Input: B[] = {1, 2, 3, 4}
Output: 20
Explanation:
For max score:
3 explodes, score= 4*2=8 (product of adjacent balloons)
2 explodes score= 4*1 + 8 = 12 (product of adjacent balloons)
1 explodes score= 4 + 12= 16 (only 4 is left on the left side)
4 explodes score = 4 + 16 = 20 (no balloons left so add 4)
score =20
other combinations will result in lesser scores.
分析:
1)目的是找到最高分
2)最大分数取决于邻居上的分数,但是没有简单的方法来找到给出最大分数的序列,因此唯一的方法是找到所有可能的序列可以从中获得最大分数。
3)由于顺序对于输入N至关重要,因此我们可以有N!序列,即nPn方式(第一个气球N方式,第二个N-1方式…最后一个气球1方式N *(N-1)(N-2).. 2 * 1 = N!
复杂:
生成所有序列O(N!)
要获得1个序列的得分,对于序列中的每个气球,我们需要左右邻居最坏的情况需要在数组中完全遍历,因此复杂度为O(N * N)
总复杂度为O(N!)* O(N * N)(注意:计算已在每个序列的末尾完成)
50 TC,N 50 *是O(N!* N * N)=> 50 * 100 * 10! => 5000 * 3628800 => 1.5 * 10 ^ 10,这无法在给定的3秒内执行(每秒10 ^ 9条指令)。
所以需要寻找优化
伪代码生成所有序列:
INPUT[N]
CHOICE[N] <= -1 //initialize to -1
Permute(0)
Permute(Position)
{
// stop condition
If ( all balloon shot )
{
Compute the score for this sequence in CHOICE[]
If score better than previous then store
}
For i:0~N-1
{
If (ith balloon not selected // CHOICE[i]==-1)
{
Select ith balloon // CHOICE[Position]= i
Permute (Position+1)
Unselect ith balloon// CHOICE[Position]= -1
}
}
}
优化:
我们可以看到在上面的算法中2个主要操作都在执行
(1)生成所有序列O(N!)
(2)计算每个序列的分数O(N * N)
我们无法优化算法以生成所有序列,但是可以进一步减少计算部分。
优化计算部分
如果可以优化找到O(1)的邻居,我们可以将计算部分减少到O(N),这会使我们的算法以1.5 * 10 ^ 9的速度执行,这可以在3秒钟内实现。
或者,我们可以计算每个选定气球的得分,以“随时随地”射击,在每次选择气球时发现邻居是额外的,可以是O(N),也可以减少1.5 * 10 ^ 9
获取邻居的算法:
O(N)的朴素方法:
邻居(选择)
对于左:如果未选择左气球中断,则选择1〜0;否则,选择0。
对于右:如果未选择右气球中断,则选择+ 1〜N-1。
如果(右== N)
右= -1;
返回左右;
通过O(1)优化的方式
1.保留2个数组left []和right [],它们包含每个气球的邻居。
2.最初知道邻居,因为第一个气球左为i-1,右气球为i + 1,除了第一个气球没有左,最后一个气球没有右。
3.选择气球时,我们可以获得它的右侧,由O(1)
4.射击气球时,更新邻居左[i + 1] =左[i]右[i-1] =右[i]
笔记:
代替调用新函数来在递归派系内左右计算,将减少许多隐藏指令,因为调用新函数编译器添加许多可以减少的指令
替代方式:
随时随地计算分数的方法
将当前得分变量传递给递归函数
选择气球射击时,请取得左右邻居
计算通过射击选定气球获得的分数
将其添加到给定的分数并传递到下一个级别
伪代码:
Permute(Position, score)
{
// stop condition
If( all balloon shot )
{
If score better than previous then store
}
For i:0~N-1
{
If (ith balloon not selected // CHOICE[i]==-1)
{
Select ith balloon // CHOICE[Position]= i
Gain = Compute the gain by shooting ith balloon
Permute (Position+1, score+ Gain)
Unselect ith balloon// CHOICE[Position]= -1
}
}
}
替代的优化方法(分而治之)和动态编程
最初的问题似乎并不像是分而治之的问题。
原因:如果我们选择一个气球(用于爆破),那么我们的阵列将被分为两个子阵列。但是,这两个子数组不会是独立的子问题。
例子
考虑5个气球B1,..,B5。突发B3将数组分为两个子数组{B1,B2}和{B4,B5}。但是,这两个子数组不是彼此独立的。突发B4的得分取决于{B1,B2}的突发顺序。
关键见解
1.要将问题分为两半,我们必须确保一半的任何动作(气球爆炸)不会影响另一半的分数。
2,如果我们修复了一个气球并确保不会破裂,直到我们将其左侧的所有气球和右侧的所有气球都破裂,那么我们就可以将问题成功地分为两个子问题。
例子
考虑前面五个气球的情况。现在,我们决定不对B3进行突发处理,而是将在使{B1,B2}和{B4,B5}彼此独立的所有气球之后对B3进行突发处理,即突发B4的分数现在独立于{B1,B2}。
可视化分而治之方法的另一种方式是,我们反过来思考问题。平行问题将被赋予一组n个放气的气球,每个气球都有一个分数,选择充气气球的顺序。用于使气球膨胀的分数等于附接到位于所述气球的左右两侧的气球的分数的乘积。
以下是递归解决方案:
#include
using namespace std;
// recursive function to generate scores
int getmaxscore(int arr[], int l, int r, int n)
{
int mscore = 0;
for (int i = l + 1; i < r; i++) {
// to permute through all cases
int cs = getmaxscore(arr, l, i, n) + getmaxscore(arr, i, r, n);
if (l == 0 && r == n)
cs = cs + arr[i];
else
cs = cs + (arr[l] * arr[r]);
if (cs > mscore)
mscore = cs;
}
return mscore;
}
int main() // driver function
{
int n = 4; // no of balloons
// assigning scores to each balloon 1-based indexing
// arr[0]=1 because to calculate score when no
// balloons are left after popping
// arr[5]=1 because to calculate score when no
// balloons are left after popping
// scores of balloons are assigned from 1 to 4 i.e 1 to n
int arr[] = { 1, 1, 2, 3, 4, 1 };
/* for input input arr[n+2],
arr[0]=1 && arr[n+1]=1
cin>>n;
for(int i=1;i<=n;i++)
cin>>arr[i]; */
cout << getmaxscore(arr, 0, n + 1, n + 1) << "\n";
return 0;
}
Output:
20