啊~~
我来了,
蒟蒻!!!
十分无厘头!
今天我们来介绍一下线性dp的进阶区间dp
对于区间dp来说,是一类题型,也是dp的重要考点、
对于动态规划,我们知道“阶段”是最重要的,那区间dp的状态就是区间长度。
它常常来解决一些区间问题,由于一些区间太大,所以我们将大区间化为小区间,然后将小区间进行动态规划,最后再一步步合成所求区间。
这就是区间dp的基本思路。当然我们一会儿将会提到记忆化搜索与区间dp的结合应用。
首先,我们来看一道基础区间dp
题目描述
设有N堆沙子排成一排,其编号为1,2,3,…,N(N<=300)。每堆沙子有一定的数量,可以用一个整数来描述,现在要将这N堆沙子合并成为一堆,每次只能合并相邻的两堆,合并的代价为这两堆沙子的数量之和,合并后与这两堆沙子相邻的沙子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同,如有4堆沙子分别为 1 3 5 2 我们可以先合并1、2堆,代价为4,得到4 5 2 又合并 1,2堆,代价为9,得到9 2 ,再合并得到11,总代价为4+9+11=24,如果第二步是先合并2,3堆,则代价为7,得到4 7,最后一次合并代价为11,总代价为4+7+11=22;问题是:找出一种合理的方法,使总的代价最小。输出最小代价。
输入格式
第一行一个数N表示沙子的堆数N。
第二行N个数,表示每堆沙子的质量(<=1000)。
输出格式
合并的最小代价
样例输入
4 1 3 5 2
样例输出
22
看完题,我们一种暴力的想法就是一个一个枚举,当然这样我们会理所当然的TLE(傻子都知道好吗!!)
这时,我们可以换个思路,我们假设两堆石子l和r,当两堆石子可以被合并时,代表l到r中间的所有石子都已经被合并了,只有这样才能时l和r两堆石子相邻。
且其中的石子数量是∑ri=lAi,所以,我们可以知道一定存在一个k(l≤k<r),可以使l到r中间的所有石子合并起来。即:
在[l,k]的石子与[k+1,r]之间的石子合并,并最终得到[l,r]之间的石子。
所以,我们就可以推出状态转移方程:
1 for(int len=1;len<=n;len++){//表示阶段,表示每一次最多可以有几堆被合并 2 for(int l=1;l<=n-len+1;l++){//表示状态,左端点 3 int r=l+len-1;//表示右端点 4 for(int k=l;k<r;k++){//决策点,又名中途转折点 5 f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+sum[r]-sum[l-1]);//状态转移方程 6 }
这样我们就解决这个问题,最后处理一下答案然后输出即可。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define maxn 307 4 int f[maxn][maxn],n,sum[maxn],a[maxn]; 5 //template<typename __Type_of_scan> 6 //void scan(__Type_of_scan &x){ 7 // __Type_of_scan f=1;x=0;char s=getchar(); 8 // while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();} 9 // while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();} 10 // x*=f; 11 //} 12 int main(){ 13 // scan(n); 14 scanf("%d",&n); 15 // memset(sum,0,sizeof(sum)); 16 memset(f,0x3f,sizeof(f));//初始化 17 for(int i=1;i<=n;i++){ 18 // scan(a[i]); 19 scanf("%d",&a[i]); 20 f[i][i]=0;//进行清零 21 sum[i]=sum[i-1]+a[i];//求前缀和 22 } 23 // for(int i=1;i<=n;i++){ 24 // f[i][i]=0; 25 // sum[i]=sum[i-1]+a[i]; 26 // } 27 for(int len=1;len<=n;len++){//表示阶段,表示每一次最多可以有几堆被合并 28 for(int l=1;l<=n-len+1;l++){//表示状态,左端点 29 int r=l+len-1;//表示右端点 30 for(int k=l;k<r;k++){//决策点,又名中途转折点 31 f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+sum[r]-sum[l-1]);//状态转移方程 32 } 33 // f[l][r]+=sum[r]-sum[l-1];//更新每个点的权值 34 } 35 } 36 // int minn=0x3f; 37 // int maxx=0; 38 // for(int i=1;i<=n;i++){ 39 // minn=min(minn,f[i][n-i+1]); 40 // maxx=max(maxx,f[i][n-i+1]); 41 // } 42 // printf("%d ",minn); 43 // printf("%d ",maxx); 44 printf("%d",f[1][n]); 45 return 0; 46 }
下面,我们来看一看加大难度的石子合并
题目描述:
题目描述
在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分.
输入输出格式
输入格式:数据的第1行试正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数.
输出格式:输出共2行,第1行为最小得分,第2行为最大得分.
输入输出样例
4 4 5 9 4
43 54
提示一下:把环变成链即可
代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define maxn 307 4 int f1[maxn][maxn],n,sum[maxn],a[maxn],f2[maxn][maxn]; 5 //template<typename __Type_of_scan> 6 //void scan(__Type_of_scan &x){ 7 // __Type_of_scan f=1;x=0;char s=getchar(); 8 // while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();} 9 // while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();} 10 // x*=f; 11 //} 12 int main(){ 13 // scan(n); 14 scanf("%d",&n); 15 // memset(sum,0,sizeof(sum)); 16 // memset(f2,0x3f,sizeof(f2));//最大值数组初始化 17 for(int i=1;i<=n;i++){ 18 // scan(a[i]); 19 scanf("%d",&a[i]); 20 a[i+n]=a[i];//因为是环,所以要存两倍数 21 // f2[i][i]=0; 22 // f1[i][i]=0;//进行清零 23 // sum[i]=sum[i-1]+a[i];//求前缀和 24 } 25 for(int i=1;i<=n*2;i++){ 26 f1[i][i]=0; 27 f2[i][i]=0; 28 sum[i]=sum[i-1]+a[i];}//求前缀和 29 for(int len=2;len<=n;len++){//表示阶段,表示每一次最多可以有几堆被合并 30 for(int l=1;l<=2*n-len;l++){//表示状态,左端点 31 int r=l+len-1;//表示右端点 32 f2[l][r]=0x3f3f3f3f;//每一次p跑最小值时都要把这个点f2初始为最大 33 for(int k=l;k<r;k++){//决策点,又名中途转折点,区间断点 34 f1[l][r]=max(f1[l][r],f1[l][k]+f1[k+1][r]);//f1表示最大值状态转移方程 35 f2[l][r]=min(f2[l][r],f2[l][k]+f2[k+1][r]);//f2表示最小值状态转移方程 36 } 37 f1[l][r]+=(sum[r]-sum[l-1]);//更新每个点的权值 38 f2[l][r]+=(sum[r]-sum[l-1]); 39 } 40 } 41 // for(int l=2;l<=n;l++){ 42 // for(int p=1;p<=2*n-l;p++){ 43 // int j=p+l-1; 44 // f1[p][j]=0; //f1表示最大值,f2表示最小值 45 // f2[p][j]=1926817; //初始化最大最小值 46 // for(int k=p;k<j;k++){ //k表示区间断点 47 // f1[p][j]=max(f1[p][j],f1[p][k]+f1[k+1][j]); // 48 // f2[p][j]=min(f2[p][j],f2[p][k]+f2[k+1][j]); // 49 // } 50 // f1[p][j]+=(sum[j]-sum[p-1]); 51 // f2[p][j]+=(sum[j]-sum[p-1]); 52 // } 53 // } 54 // int minn=0x3f; 55 // int maxx=0; 56 // for(int i=1;i<=n;i++){ 57 // minn=min(minn,f1[i][n-i+1]); 58 // maxx=max(maxx,f2[i][n-i+1]); 59 // } 60 int dp1=0; 61 int dp2=0x3f3f3f3f; 62 for(int i=1;i<=n;i++){ 63 dp1=max(dp1,f1[i][i+n-1]); 64 dp2=min(dp2,f2[i][i+n-1]); 65 } 66 printf("%d ",dp2); 67 printf("%d ",dp1); 68 // printf("%d",f[1][n]); 69 return 0; 70 }
于是,看完石子合并这个十分基础的题,我们来看一看一道poj1179
这也是一道十分经典的区间dp的题,大家可以做一做,给点提示:把环变链再缩短,最后出答案(注意最大最小值的更新)。
这道题的→_→ 题解~~
在给大家推荐一道题,是一道《算法进阶指南》的题,金字塔
区间dp题目推荐(持续更新)