洛谷P1063 能量项链
题目很长啊,大概意思就是这样
能量项链上有N颗能量珠。能量珠有一个头标记和一个尾标记(即x,y)并且,对于相邻的两颗珠子,前一颗珠子的尾标记一定等于后一颗珠子的头标记。
如果前一颗能量珠的头标记为m,尾标记为r,后一颗能量珠的头标记为r,尾标记为n,则聚合后释放的能量为m*r*n(Mars单位),新产生的珠子的头标记为m,尾标记为n。
把一串的珠子两两合并(必须要是相邻的,且按顺序),使一串项链释放出的总能量最大。
求最大能量
例如:设N=4,4颗珠子的头标记与尾标记依次为(2,3) (3,5) (5,10) (10,2)。我们用记号⊕表示两颗珠子的聚合操作,(j⊕k)表示第j,k两颗珠子聚合后所释放的能量。则第4、1两颗珠子聚合后释放的能量为:
(4⊕1)=10*2*3=60。
这一串项链可以得到最优值的一个聚合顺序所释放的总能量为
((4⊕1)⊕2)⊕3)=10*2*3+10*3*5+10*5*10=710。
注意!项链是环来得。题目也好心给出了
至于珠子的顺序,你可以这样确定:将项链放到桌面上,不要出现交叉,随意指定第一颗珠子,然后按顺时针方向确定其他珠子的顺序
如果我告诉你这是DP,是不是有点蒙?
不急不急,我们先来看看这道题变形前的样子
石子合并
(不好意思我怎么都找不到好的来源)
题意:
在操场上沿一直线排列着 n堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的两堆石子合并成新的一堆, 并将新的一堆石子数记为该次合并的得分。
计算将n堆石子合并成一堆的最小得分。
【输入格式】
第1行是石子堆数n≤100;
以下n行,每行一个整数,为各堆石子数(≤10000)。
【输出格式】
输出合并的最小得分。
样例输入
7
13 7 8 16 21 4 18
样例输出
239
题意简单了很多啊。
PS:分数的计算方式:每一次合并时,分数+=这两个石子的和
合并得到的这个石子的值为原来的两个石子的和
这道题应该是背包来得。
#include<cstdio>
#include<cstring>
using namespace std;
int n,f[100][100],a[100],sum[100][100];
int mymin(int x,int y) {return x>y?y:x;}//min
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
memset(f,63,sizeof(f)); //将f数组全部设为int的最大值
for(int i=1;i<=n;i++)
{
f[i][i]=0;//因为自己和自己不能合并,为0
sum[i][i]=a[i]; //sum[i][j]:第i个到第j个石子的和
for(int j=i+1;j<=n;j++)sum[i][j]=sum[i][j-1]+a[j]; //累加
//f[i][j]:合并i~j个石子时的最优情况
}
for(int x=2;x<=n;x++)
//第一层,x个石子依次合并的情况
{
for(int i=1;i<=n-x+1;i++)
{
int j=i+x-1;
//解释i和j:就像一个箱子上有一个可以左右移动的开口,能看到从i到j的连续的x个石子(能看到i这个点但是看不到j),i:最左端,j:最右端 。但i,j都不能超过整个箱子的左右端点
for(int k=i;k<=j-1;k++)
//解释k:我们在i~j这一段取一个点k,这样就分成了i~k和k+1~j两部分。分别计算出两边的最小值,就可以得出i~j的最小值
{
f[i][j]=mymin(f[i][j],f[i][k]+f[k+1][j]+sum[i][j]); //分数计算
}
}
}
printf("%d\n",f[1][n]); //输出合并1~n的情况
}
让我们回到能量项链这道题
能量项链相比石子合并是不是只是一个变形?
每个石子由1个值改为两个值
线改成环
最小值改成最大值不就是一个min和max的区别吗
应该没什么太大不同了。。。
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int f[210][210];
struct node
{
int x,y; //头标记和尾标记
}a[210];
int main()
{
int n;
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i].x);
a[i-1].y=a[i].x; //前一个珠子的尾标记等于后一个珠子的头标记
}
a[n].y=a[1].x; //注意是环!收尾相连
for (int i=1;i<n;i++)
a[i+n]=a[i]; //复制一段在后边,那么从i~i+n就是看成是一个从i开始的一条线了,省了一条循环
memset(f,0,sizeof(f)); //赋0
//f[i][j]:合并第i~j个珠子时的最优情况
for (int k=1;k<=n;k++) //枚举长度(与合并石子的x相似)
{
for (int i=1;i<2*n-k;i++) //从第i个石子开始断,,那么从i~i+n就是一条线
{
for (int j=i;j<i+k;j++) //在i~i+k中选一个点j分成i~j和j+1~i+k两段(与合并石子的k相同)
{
f[i][i+k]=max(f[i][i+k],f[i][j]+f[j+1][i+k]+a[i].x*a[j].y*a[i+k].y); //计算能量
}
}
}
int ans=0;
for (int i=1;i<=n;i++)
{
ans=max(f[i][i+n-1],ans);
//扫一遍,看看从哪里断得到的值最大
}
printf("%d",ans);
return 0;
}