4月24日一周学习总结
区间dp问题
这周继续学习了区间dp问题,还做了线性dp的问题,还认识了背包问题,对于区间dp问题,个人感觉思路较为统一,一般都为三层循环,第一层为区间长度,然后遍历所有区间,再嵌套一层循环,设置标志点,将一个区间分为两段,再在每个区间里取值,然后遍历所有可能。
例题:石子问题(非常经典的区间问题,个人感觉题解可以作为区间dp的一般模板)
共n堆的石子。现要将石子有序地合并成一堆。规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆石子数记为该次合并的得分。求合在一起的最小得分和最大得分。
#include <bits/stdc++.h>
using namespace std;
int w[105],dp[105][105],dp1[105][105],sum[105];
const int inf=0x3f;
int main()
{
int n;
while(cin>>n)
{
memset(dp,0,sizeof(dp));
memset(dp1,inf,sizeof(dp));
memset(sum,0,sizeof(sum));
for(int i=1; i<=n; i++)
{
cin>>w[i];
sum[i]=sum[i-1]+w[i];
dp1[i][i]=0;
}
for(int len=2; len<=n; len++)
{
for(int i=1; i<=n; i++)
{
int j=i+len-1;
for(int k=i; k<j; k++)
{
dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);
dp1[i][j]=min(dp1[i][j],dp1[i][k]+dp1[k+1][j]+sum[j]-sum[i-1]);
}
}
}
cout<<dp1[1][n]<<" "<<dp[1][n]<<endl;
}
return 0;
}
这道题就是十分经典的区间dp问题,
线性dp问题
搬寝室问题
这道题印象比较深刻的原因主要是寒假听过,也在杭电上交过,这道题动态转移方程一开始并不好想(当时没有做过买票这道题),因为一直在想如何先挑出k件物品,还是老师上课时教会的的。。。
题意:一个人搬宿舍,一次可以同时拿两件物品,两件物体的差值的平方是疲劳度,求n件物品中搬k件的最小疲劳值。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int dp[2010][1010],a[2010];
int main()
{
int n,k,i,j;
while(cin>>n>>k)
{
for(i=1;i<=n;i++)
cin>>a[i];
sort(a+1,a+n+1);
memset(dp,0,sizeof(dp));
dp[2][1]=(a[2]-a[1])*(a[2]-a[1]);dp[1][1]=999999999;
for(i=2;i<=n;i++)
{
for(j=1;j<=k;j++)
{
if(2*j<=i)
dp[i][j]=min(dp[i-2][j-1]+(a[i]-a[i-1])*(a[i]-a[i-1]),dp[i-1][j]);
else dp[i][j]=999999999;
}
}
cout<<dp[n][k]<<endl;
}
return 0;
}
当时上课的老师还讲过,如果已经找出k件的话,可以从几何的角度去分析这道题,将条件所给的平方看成面积来做,相邻物体的体积一定小于有间隔的物体,这时候没有调k件的限制,比较好想,现在有了k件就有用dp来进行一次遍历。对于一件物品,无非是拿还是不拿,不拿就的话动态转移方程就继承上一个的疲劳度,如果是拿的话,就要在继承倒数第二个疲劳度的前提下,再加上这次拿的物品与上一个物品构成的的组合,得到这次的疲劳度。(个人感觉类似于买票的那一道题,当前情况都是对于上一次情况和倒数第二层情况的继承和添加)
本周学习感悟与收获
这周是做区间dp的第一周,做的题目还是不算多,一般区间dp的模板都可以套用,但是难点还是在于分析好题目中存在的条件,识别出区间dp的问题,然后再开始根据条件进行循环。区间的本质和线性dp还是非常类似的的,可以从线性dp思维作为切入点,找到dp的方法以及动态转移方程,然后再开始思考如何实现区间dp的区间长度外循环,想要学好区间dp还是要再加深对线性dp的认识和应用。