dp中滚动数组的应用:01背包、POJ 1159 Palindrome、多段图路径问题
2017-09-14 19:51
656 查看
滚动数组用于对空间复杂度的优化,并不能减少程序时间复杂度。
1.朴素的0-1背包问题:
d[i][j]表示将前i个物品放入容量为j的背包的最大价值。
首先给出一般方法:
使用滚动数组后的循环体与结果输出:
2.POJ 1159 Palindrome
未优化(MLE):
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=5010;
int N,d[maxn][maxn];
char s[maxn];
int main(){
while(~scanf("%d",&N)){
scanf("%s",s+1);
memset(d,0,sizeof(d));
for(int i=N;i>=1;i--)
for(int j=i+1;j<=N;j++){
if(s[i]==s[j]) d[i][j]=d[i+1][j-1];
else d[i][j]=min(d[i+1][j],d[i][j-1])+1;
}
printf("%d\n",d[1]
);
}
return 0;
}
使用滚动数组:
#include <cstdio>
#include <cstring>
#include <alg
4000
orithm>
using namespace std;
const int maxn=5010;
int N,d[2][maxn];
char s[maxn];
int main(){
while(~scanf("%d",&N)){
scanf("%s",s+1);
memset(d,0,sizeof(d));
int cur=0;
for(int i=N;i>=1;i--){
cur^=1;
for(int j=i+1;j<=N;j++){
if(s[i]==s[j]) d[cur][j]=d[1-cur][j-1];
else d[cur][j]=min(d[1-cur][j],d[cur][j-1])+1;
}
}
printf("%d\n",d[cur]
);
}
return 0;
}
3.多段路径问题
有如图的有向图,从左到右共有n个节点,从上到下有m个节点,给出有向边,求出到达最后一列各点的路径总数。
(上图即程序中样例对应的有向图)
首先给出朴素算法:
d[i][j]:到达第i列,第j行节点的路径总数
a[i][j][k]:第i列,第j行节点所连接的第i+1列的节点的行标号
然后给出使用滚动数组优化的循环体与结果输出:
在每次对于列的枚举循环(i的for循环)中,改变当前cur值。
其中对于d数组,标号为cur的表示当前列的各节点的d值,即对应与第i+1列,标号为1-cur的表示上一列各节点d值,即第i列。
注意每次需要将当前cur的d值清空。
此问题与0-1背包不同的是,后者是一维的问题,只需要使用前一个状态的一个值,而本问题是二维的,需要计算各个j的d值,而计算它们还需用到上个状态的各j的值,若直接覆盖则会丢失数据。故需要用到标记cur保存上一个状态的信息。
4.需要前两个状态的多段图问题
问题2的变形,即对于第i列节点,i-1、i-2列的节点均可到达。
(上图为下面程序中样例对应的有向图)
a[i][j][k][t]:第i列,第j行节点所连接的列的节点的行标号,当t=0,表示连接第i+1列,当t=1表示连接第i+2列。
直接给出使用滚动数组的代码:
可以看到若需要前两个状态,则需要3个cur值,cur值应不断模3增加。
1.朴素的0-1背包问题:
d[i][j]表示将前i个物品放入容量为j的背包的最大价值。
首先给出一般方法:
#include <iostream> #include <cstring> using namespace std; const int maxn=50,maxv=100; int v,w,n,d[maxn][maxv],V; /*Sample input: 5 12 2 3 4 8 1 2 3 5 5 11 Sample output: 6 */ int main(){ while(cin>>n>>V){ memset(d,0,sizeof(d)); for(int i=1;i<=n;i++){ cin>>w>>v; for(int j=V;j>=v;j--) d[i][j]=max(d[i-1][j],d[i-1][j-v]+w); } cout<<d [V]<<endl; } return 0; }
使用滚动数组后的循环体与结果输出:
for(int i=1;i<=n;i++){ cin>>w>>v; for(int j=V;j>=v;j--) d[j]=max(d[j],d[j-v]+w); } cout<<d[V]<<endl;
2.POJ 1159 Palindrome
未优化(MLE):
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=5010;
int N,d[maxn][maxn];
char s[maxn];
int main(){
while(~scanf("%d",&N)){
scanf("%s",s+1);
memset(d,0,sizeof(d));
for(int i=N;i>=1;i--)
for(int j=i+1;j<=N;j++){
if(s[i]==s[j]) d[i][j]=d[i+1][j-1];
else d[i][j]=min(d[i+1][j],d[i][j-1])+1;
}
printf("%d\n",d[1]
);
}
return 0;
}
使用滚动数组:
#include <cstdio>
#include <cstring>
#include <alg
4000
orithm>
using namespace std;
const int maxn=5010;
int N,d[2][maxn];
char s[maxn];
int main(){
while(~scanf("%d",&N)){
scanf("%s",s+1);
memset(d,0,sizeof(d));
int cur=0;
for(int i=N;i>=1;i--){
cur^=1;
for(int j=i+1;j<=N;j++){
if(s[i]==s[j]) d[cur][j]=d[1-cur][j-1];
else d[cur][j]=min(d[1-cur][j],d[cur][j-1])+1;
}
}
printf("%d\n",d[cur]
);
}
return 0;
}
3.多段路径问题
有如图的有向图,从左到右共有n个节点,从上到下有m个节点,给出有向边,求出到达最后一列各点的路径总数。
(上图即程序中样例对应的有向图)
首先给出朴素算法:
d[i][j]:到达第i列,第j行节点的路径总数
a[i][j][k]:第i列,第j行节点所连接的第i+1列的节点的行标号
#include <iostream> using namespace std; int m,n,d[50][50],a[50][50][50]; /*sample input: 3 3 3 1 2 3 1 2 2 1 3 1 2 2 1 3 1 2 sample answer: (a=1) 2 (a=2) 4 (a=3) 2 */ int main(){ while(cin>>m>>n){ // int cur=0; for(int i=1;i<=n-1;i++) for(int j=1;j<=m;j++){ int cnt; cin>>cnt; for(int k=1;k<=cnt;k++) cin>>a[i][j][k]; a[i][j][0]=cnt; } cout<<"end"<<endl; for(int i=1;i<=n-1;i++) for(int j=1;j<=m;j++){ if(i==1) d[i][j]=1; for(int k=1;k<=a[i][j][0];k++) d[i+1][a[i][j][k]]+=d[i][j]; } int a; while(cin>>a) cout<<d [a]<<endl; } }
然后给出使用滚动数组优化的循环体与结果输出:
for(int j=1;j<=m;j++) d[0][j]=1; for(int i=1;i<=n-1;i++){ cur^=1; memset(d[cur],0,sizeof(d[cur])); for(int j=1;j<=m;j++) for(int k=1;k<=a[i][j][0];k++) d[cur][a[i][j][k]]+=d[1-cur][j]; } int a; while(cin>>a) cout<<d[cur][a]<<endl;
在每次对于列的枚举循环(i的for循环)中,改变当前cur值。
其中对于d数组,标号为cur的表示当前列的各节点的d值,即对应与第i+1列,标号为1-cur的表示上一列各节点d值,即第i列。
注意每次需要将当前cur的d值清空。
此问题与0-1背包不同的是,后者是一维的问题,只需要使用前一个状态的一个值,而本问题是二维的,需要计算各个j的d值,而计算它们还需用到上个状态的各j的值,若直接覆盖则会丢失数据。故需要用到标记cur保存上一个状态的信息。
4.需要前两个状态的多段图问题
问题2的变形,即对于第i列节点,i-1、i-2列的节点均可到达。
(上图为下面程序中样例对应的有向图)
a[i][j][k][t]:第i列,第j行节点所连接的列的节点的行标号,当t=0,表示连接第i+1列,当t=1表示连接第i+2列。
直接给出使用滚动数组的代码:
#include <iostream> #include <cstring> #include <cstdio> using namespace std; const int maxn=55; int n,m,a[maxn][maxn][maxn][2],d[3][maxn]; /*Sample Input: 3 3 3 1 2 3 0 1 2 2 1 3 2 1 3 1 3 2 2 3 0 1 1 0 1 2 0 Sample output: 3 (a==1) 4 (a==2) 4 (a==3) */ int main(){ while(cin>>n>>m){ memset(a,0,sizeof(a)); for(int i=1;i<=n-1;i++) for(int j=1;j<=m;j++) for(int t=0;t<2;t++){ int temp; cin>>temp; for(int k=1;k<=temp;k++) cin>>a[i][j][k][t]; a[i][j][0][t]=temp; } cout<<"end"<<endl; int cur=0; for(int j=1;j<=m;j++) d[0][j]=1; for(int i=1;i<=n-1;i++){ cur=(cur+1)%3; memset(d[cur],0,sizeof(d[cur])); for(int j=1;j<=m;j++) for(int t=0;t<2;t++) for(int k=1;k<=a[i-t][j][0][t];k++) d[cur][a[i-t][j][k][t]]+=d[(cur+2-t)%3][j]; } int a; while(cin>>a) cout<<d[cur][a]<<endl; } }其中对于d数组,标号为cur的表示当前列的各节点的d值,即对应于第i+1列;标号为(cur+2)%3对应前一列,即第i列;标号为(cur+1)%3的对应于向前数两列,即第i-1列。
可以看到若需要前两个状态,则需要3个cur值,cur值应不断模3增加。
相关文章推荐
- poj - 1159 - Palindrome(滚动数组dp)
- POJ1159——Palindrome——DP+滚动数组(节省空间)
- [POJ1159]Palindrome(dp,滚动数组)
- POJ1159 Palindrome(滚动数组DP)
- poj1159--Palindrome(dp:最长公共子序列变形 + 滚动数组)
- POJ 1159 Palindrome(区间DP/最长公共子序列+滚动数组)
- POJ 1159 Palindrome(区间DP/最长公共子序列+滚动数组)
- POJ 1159 Palindrome (滚动数组 DP)
- POJ3624---01背包入门 dp+滚动数组+重复利用一维数组
- poj 1159 Palindrome ( dp 滚动数组 )
- POJ 1159-Palindrome(dp_回文串+滚动数组)
- poj 1159 Palindrome(dp+滚动数组)
- poj1159--Palindrome(dp:最长公共子序列变形 + 滚动数组)
- poj 1159 Palindrome (DP:LCS+滚动数组)
- 见微知著----POJ 1159(线性DP问题+滚动数组)
- POJ-1159 Palindrome (dp+滚动数组节省内存)
- poj1159 palindrome(动态规划+滚动数组)
- poj-1159-Palindrome【LCS】【滚动数组】
- POJ1159——Palindrome(最长公共子序列+滚动数组)
- poj-1159-Palindrome-学习滚动数组