【动态规划】魔法冰激凌(msyrup)
问题
题目描述:
得到一种冰激凌有两种方法:可以按照魔法书上的指导自己配置,也可以到魔法商店里去买——那里对于每种冰激凌都有供应,虽然有可能价格很贵。在魔法书上有很多这样的记载:
1 份A 冰激凌混合1 份B 冰激凌就可以得到1 份C 冰激凌。(至于为什么1+1=1,因为……这是魔法世界)好了,现在你知道了需要得到某种冰激凌,还知道所有可能涉及到的冰激凌的价格以及魔法书上所有的配置方法,现在要问的就是:1.最少花多少钱可以配制成功这种珍贵的冰激凌;
2.共有多少种不同的花费最少的方案(两种可行的配置方案如果有任何一个步骤不同则视为不同的)。假定初始时你手中并没有任何可以用的冰激凌。
输入格式:
第一行有一个整数N,表示一共涉及到的冰激凌总数。冰激凌从0~N1
顺序编号,0 号冰激凌就是最终要配制的冰激凌。
第二行有N 个整数,分别表示从0~N1
顺序编号的所有冰激凌在魔法商店的价格(都表示1份的价格)。
第三行开始,每行有3 个整数A、B、C,表示1 份A 冰激凌混合1 份B 冰激凌就可以得到1 份C 冰激凌。注意,某两种特定的冰激凌搭配如果能配成新冰激凌的话,那么结果是唯一的。也就是说不会出现某两行的A、B 相同但C 不同的情况。
输入以一个空行结束。
输出格式:
输出两个用空格隔开的整数,分别表示得到0 号冰激凌的最小花费以及花费最少的方案的个数。
样例输入:
7
10 5 6 3 2 2 3
1 2 0
4 5 1
3 6 2
样例输出:
10 3
样例说明:
最优方案有3 种,分别是:直接买0 号冰激凌;买4 号冰激凌、5 号冰激凌配制成1 号冰激凌,直接买2 号冰激凌,然后配制成0 号冰激凌;买4 号冰激凌、5 号冰激凌配制成1 号冰激凌,买3 号冰激凌、6号冰激凌配制成2,然后配制成0。
数据范围:n<=1000
分析
读完题我们就会想到动态规划的方法,而且方程也很好想到,设f[i]表示买第i个冰激凌需要花费的最少的钱数。ff[i]表示用最优方案买i冰激凌的方案总数。
f[i]=min{f[i],f[k]+f[j]}(i,j表示能够拼成i的所有的方案)(初值为a[i])
ff[i]=ff[k]*ff[j](f[i]<f[k]+f[j])乘法原理(初值为1)
ff[i]=ff[i]+ff[k]+ff[j]加法原理
看似问题已经解决,但这里出现了一个问题,如何实现方程呢?一般的动态规划或用记忆化搜索或用循环,前者是从最终状态递归搜索直到达到已知状态,后者是从已知状态递推最终状态。显然无论用哪种方法我们都不能保证在更新i的时候j和k已经是最优的。也就是说这样的动态规划不符合子结构最优性原理。
能否构建图论模型呢?
我们想到了dijistra算法,每次在所有f[i]中选取最小,用这个节点及与之相关的节点(两个节点可拼成新节点)更新其他节点。这样就满足了子结构最优性。
相当于将所有节点分成了两个集合,a集合代表已经确定其最优值,b集合代表最优值未确定(通过v数组分类)。之后,每次贪心选取a集合中的最小值,用这个节点更新未在a集合中的节点,直到所有元素都在a集合为止(n次)。显然时间复杂度为n2
其他请见代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
program liukeke;
var
f,ff:array[0..1005] of longint;
make:array[0..1005,0..1005] of longint;
v:array[0..1005] of boolean;
n,i,j,min,x,y,z,k,temp:longint;
begin
assign(input,'msyrup.in');reset(input);
assign(output,'msyrup.out');rewrite(output);
readln(n);
for i:=0 to n-1 do
begin
read(f[i]);
ff[i]:=1;
end;
fillchar(make,sizeof(make),$ff);
fillchar(v,sizeof(v),0);
while not eof do
begin
readln(x,y,z);
make[x,y]:=z;
make[y,x]:=z;
end;
for j:=0 to n-1 do
begin
min:=maxlongint;
for i:=0 to n-1 do
if(f[i]<min)and(not v[i]) then
begin
min:=f[i];
k:=i;
end;
v[k]:=true;//一旦进入a中,该子问题的最优值就已经确定
for i:=0 to n-1 do
begin
temp:=make[k,i];
if temp<>-1 then
if (f[k]+f[i]<f[temp])and(v[i]) then//两者必须都在集合a中
begin
f[temp]:=f[k]+f[i];
ff[temp]:=ff[k]*ff[i];
end else
if (f[k]+f[i]=f[temp])and(v[i]) then
ff[temp]:=ff[temp]+ff[k]*ff[i];
end;
end;
writeln(f[0],' ',ff[0]);
close(input);
close(output);
end.