HDU3480_区间DP平行四边形优化
做到现在能一眼看出来是区间DP的问题了
也能够知道dp[i][j]表示前 i 个节点被分为 j 个区间所取得的最优值的情况
cost[i][j]表示从i到j元素区间中的值,这里可以直接排序后简单求出——也就是我们的代价函数
这样其实就能够做出来了,但是空间复杂度是n3入门的题能过,普通点的都会考察你一下斜率DP的优化和四边形不等式的优化。目前我主要就懂了平行四边形的优化
首先你要确保dp和cost这两个都满足四边形不等式这个前面有过证明的博客这里就简单一提。
(博客园真的好棒,自动保存的,刚刚不小心全关了...)
( a < b <= c< d )
f[a][c]+f[b][d]<=f[b][c]+f[a][d]
这类不等式的满足就可以证明出决策函数s[i][j]满足上一等式——————决策函数s[i][j]表示dp[i][j]取得最优值时的k值,也就是把前 i 个分成 j 个区间 化为前 k 个分为 j - 1个区间留下一个区间 k +1 --- i这类问题,最优值k取s[i][j]
所以这个不等式就可以利用DP的层层递推性,来缩小k的遍历范围如下
i< i+1<=j< j+1
那么我 s[i][j-1]<=s[i][j]<=s[i+1][j]
所以对于我们的遍历j应该是正序的i应该是逆序的这样才能层层推嘛,对于决策函数的初始化
s[i][i] = i前i个分成i段的最优值怎么分都可以但是为了缩小范围所以取 i 更合适
s[i][1] 吧前i个元素,分成1个区间那么k的取值就是0呢
s[n+1][j] n + 1是 i越界的情况,可以看上面的决策函数的不等式,i+1是完全有越界的情况出现的,所以对于越界的情况我们统一赋值n也就是k的最大取值了
做了这种处理之后,剩下的就ojbk了,加油
这些东西昨天怎么想都想不出来,睡了一家,今天路上一想就通了,好不奇怪,所以不要太心急,追求效率是应该的但也要追求记忆时间额的效率呢,加油ACMer!
#include <iostream> #include <cstdio> #include <string.h> #include <algorithm> #define inf (1 << 30) using namespace std; const int maxn = 1e4 + 10; const int maxm = 5e3 + 10; int dp[maxn][maxm]; //dp[i][j]把前i个数分为j个集合所得到的最优值 int s[maxn][maxm]; //dp[i][j]的前面的状态 int a[maxn]; void init() { memset(dp,0,sizeof(dp)); memset(s,0,sizeof(s)); } int getval(int l,int r) { return (a[r] - a[l]) * (a[r] - a[l]); } int main() { int t,n,m; scanf("%d",&t); int cas = 0; while(t--) { scanf("%d%d",&n,&m); init(); for(int i = 1;i <= n;i++) { scanf("%d",&a[i]); } sort(a+1,a+1+n); //用s[i][j]表示dp[i][j]取得最优解的时候k的位置的话 //用s[i][j]来表示选dp[i][j]的最佳选择然后s[i-1][j]<=s[i][j]<=s[i][j+1] for(int i = 1;i <= n;i++) { dp[i][1] = getval(1,i); dp[i][i] = 0; s[i][i] = i; s[i][1] = 0; } for(int j = 2;j <= m;j++) { s[n+1][j] = n ;//越界情况 for(int i = n;i >= j;i--) { dp[i][j] = inf; for(int k = s[i][j-1];k <= s[i+1][j];k++) { if(dp[i][j] > dp[k][j-1] + getval(k+1,i)) { dp[i][j] = dp[k][j-1] + getval(k+1,i); s[i][j] = k; } } } } printf("Case %d: %d\n",++cas,dp[n][m]); } return 0; }
嗯嗯自己写的吧,算法有很大的优化空间,时间消耗过大,险过
而且空间占用很大,就模拟大佬的代码学习了一下滚动数组,优化空间
空间消耗过大,滚动数组
滚动数组一般在DP题和状态压缩算法方面用的多,而且优化后效率很高,推荐使用。
对什么可以滚动呢??
一共就两个东西,一个是分为区间数,一个是元素的个数、
咋一眼也就知道分的区间数应该是能滚动的,元素个数没什么好的想法
说明白一点,先看看原来没有滚动的版本
dp[i][j] > dp[k][j-1] + getval(k+1,i)
i是元素个数的限制
j是分的区间个数的限制
你看到k是要遍历的,也就是元素个数是不能压缩的,滚动数组要具备的条件就是,他一次
只用部分值,很少一部分,然后我们可以滚动存储
所以看看j,划分的区间个数,在进行更新时好像只和j-1有关呢
哈哈,空间为2的滚动数组出来了
#include <cstdio> #include <string.h> #include <algorithm> #include <iostream> #define inf (1<<30) using namespace std; const int maxn = 1e4 +10; const int maxm = 1e3 + 10; int dp[2][maxn];//这里问题不一样了第一维是分的区间个数,第二维是元素个数 int s[2][maxn]; int a[maxn]; int n,m; void init() { for(int i = 1;i <= n;i++) scanf("%d",&a[i]); sort(a+1,a+1+n); for(int i = 1;i <= n;i++) { dp[1%2][i] = (a[i] - a[1]) * (a[i] - a[1]); s[1%2][i] = 0; } } void solve() { for(int i = 2;i <= m;i++) { s[i%2][n+1] = n; for(int j = n;j > i;j--) { dp[i % 2][j] = inf; for(int k = s[(i-1)%2][j];k <= s[i%2][j+1];k++) { if(dp[(i-1) % 2][k] + (a[j] - a[k+1]) * (a[j] - a[k+1]) < dp[i%2][j]) { dp[i%2][j] = dp[(i-1) % 2][k] + (a[j] - a[k+1]) * (a[j] - a[k+1]); s[i % 2][j] = k; } } } } } int main() { int t; scanf("%d",&t); for(int ca = 1; ca <= t;ca++) { scanf("%d%d",&n,&m); init(); solve(); printf("Case %d: %d\n",ca,dp[m%2][n]); } return 0; }
然后大佬的算法时间消耗也少,真的是很不错,而且dp的维度征好和我的相反