蒟蒻吃药计划-治疗系列 #round 2 合并石子+乘积最大
1.合并石子
《信息学奥赛一本通》第五版 P371 第三节 T1
我就直接开始讲吧:
Warning:这个题目和 合并果子 不一样!不一样!不一样!不一样!不一样!不一样!不一样!不一样!
:我想告诉你一个事情,你帮帮我好么?
(内心:mmp怎么又是这个人)
:昨天我去商场的时候,钱包被偷了,银行卡啥的都没了,你能帮帮我么?
(内心:凭啥,我就不帮)
:如果你帮我找到的话,我给你50金币好不好?
(*听到这句话,你充满了决心)
好吧,那我们帮帮他吧,让我们先看看他遇到了什么问题
:那个小偷在我的钱包里留了一张纸条,上面写着一个地址,我昨天到那里去之后看到那儿有几堆石头,上面都有数字,旁边有一块石碑,要我把相邻的两对合并,最后只剩一堆,让花费的体力最小
体力是怎么计算的呢?
:两堆石子合并之后,花费的体力值为两堆石头上的数字的和
好,那我们先和他告别,自己想一想
他已经告诉我们,有 N 堆石子,每堆石子都有对应的一个数字
我们就开一个 stone 数组表示吧
不过要说明一点,为了表示方便,我们把 stone[i] 表示为前 i 堆石子的和
我们再开一个 giver 二维数组,并且让 giver[i][j] 表示为第 i 堆到第 j 堆的石子合并之后花费的最小体力值
那么,我们应该怎么去求得花费最小的体力值呢?
让我们仔细思索一下
先开个循环吧
设有一 i 并使得 i=n-1...1 作为左端点
再设一 j 使得 j=i+1...n 作为右端点
可是这样好像还不够
那我们再弄一个 k 并让 k=i...j-1 把 i 开头 j 结尾的 giver[i][j] 分成两段
用 k 枚举每一种可能的分段,一步一步推出正确答案!
根据上面的东西,我们就可以推出状态转移方程:
giver[i][j]=min(giver[i][j],giver[i][k]+giver[k+1][j]+stone[j]-stone[i-1]);
对啦!
边界条件:
giver[1...n][1...n]=0;
现在,让我们带着这两个小朋友开始破译这个问题吧!
代码如下:
1 #include <bits/stdc++.h> 2 #define fp(i,l,r) for(register int i=(l);i<=(r);++i) 3 #define fd(i,l,r) for(register int i=(l);i>=(r);--i) 4 using namespace std; 5 inline int botposs(int a,int b,int pd){ 6 if(pd==1) return a>b?a:b; 7 if(pd==0) return a>b?b:a; 8 } 9 int main(){ 10 int n; 11 int stone[100+20]={0}; 12 int giver[100+20][100+20]; 13 memset(giver,32,sizeof(giver)); 14 stone[0]=0; 15 scanf("%d",&n); 16 fp(i,1,n){ 17 int x; 18 scanf("%d",&x); 19 stone[i]=stone[i-1]+x; 20 } 21 fp(i,1,n){ 22 giver[i][i]=0; 23 } 24 fd(i,n-1,1){ 25 fp(j,i+1,n){ 26 fp(k,i,j-1){ 27 giver[i][j]=botposs(giver[i][j],giver[i][k]+giver[k+1][j]+stone[j]-stone[i-1],0); 28 } 29 } 30 } 31 printf("%d",giver[1][n]); 32 return 0; 33 }
现在我们去找他吧!
他成功了吗?
:成是成功了,钱包也找回来了,可是解开这个谜题之后,突然出现了一个山洞,里面有一个房间,乌七八黑的,我不敢进去,你再帮帮我好吗?如果成功,我给你100金币
(*你充满了决心)
好吧,我们就再答应他一次吧!为了我们的金币!
2.乘积最大
《信息学奥赛一本通》第五版 P371 第三节 T2
:你好啊,昨天我感冒了,对,就是那种,最新的,恩恩,对对对
这个家伙怎么了?这才一个晚上呢,怎么就感冒了?
:哦,可能是昨天空调开太大了,然后踢了被子,就着凉了吧
...
:对了,我们去山洞那儿吧
我们走吧
:咦,怎么到处都是警……阿阿阿阿阿阿阿阿阿阿阿阿阿阿阿阿阿阿阿阿阿阿阿阿阿阿阿阿阿阿阿阿阿嚏
看样子他咳得很严重啊,不过我们还是得先想办法进去,这儿全都是警察...
那我们作弊吧,花费20金币进去( - MDZZ还能这么玩? - 我有钱我是RMB玩家你咬我啊略略略略略)
好的,现在我们进来了,现在我们眼前有一张书桌,上面趴着一个人,手里有一支带有血迹的笔,桌子上到处都是凝固了的血液
:(loudly)哇,好可怕好血腥啊............
拜托啊你少说点话啊,被警察发现就完蛋了啊!
:(very loudly)啊?什么?
......那我们先捂住他的嘴,等警察走了之后,自己去看看
越过封锁线,我们来到书桌前,观察一下吧,加点Exp
咦?这支笔怎么像脱节了一样啊?
让我们拆开,里面有一张卷曲的纸片,上面写着几行字
我们了解一下大意啊,恩,应该就是说:
给你k个乘号,让你插入一个数,使得插入之后的各个数乘积最大,保证乘号的数量比数字的位数少起码两个
恩........................................................................................................................................
我想这道题应该可以用动态规划来完成
我们将$k$个乘号视为$k$个阶段
我们先设一$num$数组并且用$num[j][i]$表示第$j$位到第$i$位的所组成的自然数
按照套路,我们搞一个$dp$数组并且得到状态转移方程$dp[i][j]=max{dp[j][k-1]*num[j+1][i]}(k<=j<i)$
不过,代码实现时的方程应该是这样的$dp[i][k]=max(dp[i][k],dp[j][k-1]*num[j+1][i])$
加上一个边界条件$dp[j][0]=num[1][j](1<=j<=n)$
最后的代码就不放出来啦!
:唔......
啊,我们忘记他了!
他指不准又弄出了什么幺蛾子,反正100金币已经到手了,去看看吧!