这里只对区间DP的三种石子归并进行总结=w=
一、Codevs 1048
题目描述 Description
有n堆石子排成一列,每堆石子有一个重量w[i], 每次合并可以合并相邻的两堆石子,一次合并的代价为两堆石子的重量和w[i]+w[i+1]。问安排怎样的合并顺序,能够使得总合并代价达到最小。
输入描述 Input Description
第一行一个整数n(n<=100)
第二行n个整数w1,w2...wn (wi <= 100)
输出描述 Output Description
一个整数表示最小合并代价
样例输入 Sample Input
4
4 1 1 4
样例输出 Sample Output
18
这是我接触的第一道区间DP,是石子归并问题的最简单一种问题:直线型且n<=100;
也就是说,用O(n^3) 的区间DP妥妥可以过不用加任何优化和特殊处理;
当我们只有两堆石子的时候,很显然答案是唯一的:左边一堆+右边一堆;
当我们有3堆石子时,我们就出现了多种选择,我们设三堆石子分别为ABC,那么我们有以下两种合并方式((AB)C) 或 (A(BC)),就变成了两堆的情况=w=
再进一步比较,我们用ans1表示第一种合并,ans2表示第二种合并,那么
Ans1=(a[1]+a[2]))+a[1]+a[2]+a[3];
Ans2=(a[2]+a[3])+a[1]+a[2]+a[3];
以此类推,(实践出真知)
从第i堆到第j堆所需要的代价只要括号内最小就最小,最后再加上第i堆到第j堆石子总和,所以我们就需要知道如何将第i堆到第j堆分为两堆再进行合并
用f[i,j]表示第i堆到第j堆合并的最小代价,sum[i]表示前i堆的石子总数(读入时O(n)处理),用k来枚举左右区间分界
所以:
F[i,j]=min{f[i,k]+f[k+1,j]}+sum[j]-sum[i-1];
由此可知,n需要倒序处理=w=
预处理出f[i,i]:=0;(没有进行合并)
F[i,i+1]:=a[i+1]+a[i];(相邻两堆进行合并)
个人于是,妥妥的就过了=w=,高兴几秒钟
var
n,t :longint;
i,j,k :longint;
sum,a :array[0..110] of longint;
f :array[0..110,0..110] of longint;
function min(a,b:longint):longint;
begin
if a<b then exit(a) else exit(b);
end;
begin
read(n);
for i:=1 to n do read(a[i]);
for i:=1 to n do sum[i]:=sum[i-1]+a[i];
//
for i:=1 to n-1 do f[i,i+1]:=a[i]+a[i+1];
//
for i:=n downto 1 do //倒序
for j:=i+2 to n do
begin
t:=sum[j]-sum[i-1];
f[i,j]:=f[i+1,j]+t;
for k:=i+1 to j-1 do
f[i,j]:=min(f[i,j],f[i,k]+f[k+1,j]+t);
end;
writeln(f[1,n]);
end.
接下来,咱们仍然看直线型的石子合并,优化版本=A=
COdevs 3002
题目描述 Description
有n堆石子排成一列,每堆石子有一个重量w[i], 每次合并可以合并相邻的两堆石子,一次合并的代价为两堆石子的重量和w[i]+w[i+1]。问安排怎样的合并顺序,能够使得总合并代价达到最小。
输入描述 Input Description
第一行一个整数n(n<=3000)
第二行n个整数w1,w2...wn (wi <= 3000)
输出描述 Output Description
一个整数表示最小合并代价
样例输入 Sample Input
4
4 1 1 4
样例输出 Sample Output
18
数据范围及提示 Data Size & Hint
数据范围相比“石子归并” 扩大了
扩大后的数据范围显然刚才的O(n^3) 会T,事实证明按照刚才那么做会T5个,A5个;
所以我们需要优化,优化到O(n^2) 才可以,我们优化的切入点就是如果某状态不会有最优解那么咱就不不继续做了,于是就有了接下来这个东西:
四边形不等式(据说noip考不到,noi理论上会考到)
在动态规划中,有这样的一类问题
状态转移方程 dp[i][j]=min{dp[i][k]+dp[k+1][j]}+w[i][j] k>i&&k<=j O(n*n*n)
且有如下一些定义和定理:
如果一个函数w[i][j],满足 w[i][j]+w[i'][j']<=w[i][j']+w[i'][j] i<=i'<=j<=j' 则称w满足凸四边形不等式
如果一个函数w[i][j],满足 w[i'][j]<=w[i][j'] i<=i'<=j<=j' 则称w关于区间包含关系单调
定理1:如果w同时满足四边形不等式和区间单调关系,则dp也满足四边形不等式
定理2:如果定理1条件满足时让dp[i][j]取最小值的k为K[i][j],则K[i][j-1]<=K[i][j]<=K[i+1][j]
(K[i][j]是指dp[i][j]的最优决策,也就是使dp[i][j]最小时的原k的取值)
(定理2是四边形不等式优化的关键所在,它说明了决策具有单调性,然后我们可以据此来缩小决策枚举的区间,进行优化)
定理3:w为凸当且仅当 w[i][j]+w[i+1][j+1]<=w[i+1][j]+w[i][j+1]
(告诉我们验证w是否为凸的方法,就是固定一个变量,然后看成是一个一元函数,进而判断单调性。如,我们可以固定j算出w[i][j+1]-w[i][j]关于i的表达式,看它是关于i递增还是递减,如果是递减,则w为凸)
其实理论上来讲能不能用四边形不等式优化需要经过证明才行,但是,一般来讲,都是通过打表+瞪眼观察法
同时对于K[i][j-1]<=K[i][j]<=K[i+1][j] ,要根据具体含义和运算的先后顺序综合分析,不是只要满足单调性就一定是对的
所以我们求K[i][j]的时候就只与K[i][j-1]、K[i+1][j] 有关,所以就优化到了O(n^2)
那么改变状态转移方程为:
dp[i,j]=min{dp[i,k]+m[k-1,j]} (s[i,j-1]≤k≤s[i+1,j])
不难看出,复杂度决定于s的值,以求m[i,i+L]为例,
(s[2,L+1]-s[1,L])+(s[3,L+2]-s[2,L+1])…+(s[n-L+1,n]-s[n-L,n-1])=s[n-L+1,n]-s[1,L]≤n
所以总复杂度是O(n)
其余的相关证明由于我水平还是个渣渣所以无法给出 ○| ̄|_,请见谅
//
所以我们只需要在上一题的基础上再加上个数组s,改一下k的循环范围,就A了=w=
var
n :longint;
a,sum :array[0..3010] of longint;
f,s :array[0..3010,0..6010] of longint;
i,j,k :longint;
function min(a,b:longint):longint;
begin
if a<b then exit(a) else exit(b);
end;
procedure work;
begin
for i:=1 to n do f[i,i]:=0;
for i:=1 to n-1 do f[i,i+1]:=a[i]+a[i+1];
for i:=1 to n do s[i,i]:=i;
for i:=1 to n-1 do s[i,i+1]:=i;
//
for i:=n-2 downto 1 do
for j:=i+2 to n do
begin
f[i,j]:=f[i,i]+f[i+1,j];
s[i,j]:=i;
for k:=s[i,j-1] to s[i+1,j] do
begin
if f[i,j]>f[i,k]+f[k+1,j] then
begin
s[i,j]:=k;
f[i,j]:=f[i,k]+f[k+1,j];
end;
end;
f[i,j]:=f[i,j]+sum[j]-sum[i-1];
end;
end;
begin
read(n);
for i:=1 to n do
begin
read(a[i]);
sum[i]:=sum[i-1]+a[i];
end;
work;
writeln(f[1,n]);
end.
休息一分钟,眨眨眼,接下来换个思路,咱们来看看环型的石子合并=w=上菜!
Codevs 2102
题目描述 Description
在一个园形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分.
输入描述 Input Description
数据的第1行试正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数.
输出描述 Output Description
输出共2行,第1行为最小得分,第2行为最大得分.
样例输入 Sample Input
4
4 4 5 9
样例输出 Sample Output
43
54
数据范围及提示 Data Size & Hint
经典的区间动态规划。
其实在经过刚才四边形不等式的轰炸回头看看这道朴素的不需要任何优化的题顿时感觉一阵清松=w=
相比第一题,唯一的不同在于如何对环的处理,其实依旧可以按照第一题那样,因为即使是环,咱们在dp的时候依旧看成了直线型,只不过对数组a进行(n-1 )次变换来代表环形(个人习惯),由于三道题写的时间间隔太大,所以风格和方法略有不同,实际代码与我所说有所出入,但大同小异0.0
注意这道题是要求一个最大和一个最小=w=
于是,一不小心就A了=w=
var
fmin,fmax :array[0..110,0..110] of longint;
a,b :array[0..110] of longint;
n :longint;
i,j :longint;
minn,maxn :longint;
function min(a,b:longint):longint;
begin
if a<b then exit(a) else exit(b);
end;
function max(a,b:longint):longint;
begin
if a>b then exit(a) else exit(b);
end;
function find_min(n:longint):longint;
var
i,j,k:longint;
t1:longint;
begin
for i:=1 to n do
for j:=1 to n do fmin[i,j]:=-1;
//1
for i:=1 to n do fmin[i,i]:=0;
//2
for i:=1 to n-1 do fmin[i,i+1]:=a[i]+a[i+1];
//3->n
for k:=3 to n do //堆数
begin
for i:=1 to n-k+1 do //开始位置
begin
t1:=0;
for j:=i to i+k-1 do inc(t1,a[j]); //i+k-1结束位置
fmin[i,i+k-1]:=fmin[i+1,i+k-1]+t1;
for j:=i+1 to i+k-2 do //枚举断点
fmin[i,i+k-1]:=min(fmin[i,i+k-1],fmin[i,j]+fmin[j+1,i+k-1]+t1);
end;
end;
exit(fmin[1,n]);
end;
function find_max(n:longint):longint;
var
i,j,k:longint;
t2:longint;
begin
for i:=1 to n do
for j:=1 to n do fmax[i,j]:=-1;
//1
for i:=1 to n do fmax[i,i]:=0;
//2
for i:=1 to n-1 do fmax[i,i+1]:=a[i]+a[i+1];
//3->n
for k:=3 to n do
begin
for i:=1 to n-k+1 do
begin
t2:=0;
for j:=i to i+k-1 do inc(t2,a[j]);
fmax[i,i+k-1]:=fmax[i+1,i+k-1]+t2;
for j:=i+1 to i+k-2 do
fmax[i,i+k-1]:=max(fmax[i,i+k-1],fmax[i,j]+fmax[j+1,i+k-1]+t2);
end;
end;
exit(fmax[1,n]);
end;
begin
read(n);
for i:=1 to n do read(a[i]);
minn:=find_min(n);
maxn:=find_max(n);
for j:=2 to n do
begin
for i:=1 to n do b[i]:=a[i mod n+1];
a:=b;
//
minn:=min(minn,find_min(n));
maxn:=max(maxn,find_max(n));
end;
//
writeln(minn);
writeln(maxn);
end.
然后相应、相似题目也该开刷了哈=。=
仅以此篇来记录一下我的区间型DP入门=w=
——by Eirlys
转载请注明出处=w=