kuangbin 基础DP集合

编程入门 行业动态 更新时间:2024-10-26 10:28:58

kuangbin <a href=https://www.elefans.com/category/jswz/34/1770030.html style=基础DP集合"/>

kuangbin 基础DP集合

HDU 1024
第一遍水过,没有体会到这个题的奥妙,思考了很久终于体会了。
大概意思是求把序列分成m段的子序列,并不一定要覆盖完,求子序列和的最大值
我们首先要写出基本的动态转移方程:
DP:dp[ i ] [ j ] =max ( dp[ i - 1 ] [ 1~j-1 ]+a[ j ],dp[ i ] [ j - 1 ]+a[ j ] )
其中dp[ i ][ j ]其中i,j表示,第i个集合,取前j 个数。
意思是,dp[ i ][ j ]是由两个状态转移过来的,一个是把新的数归入i集合,还有一个是把新的数作为第一
个数,归入新的集合。但是显然这个DP方程显得过于臃肿,我们想办法化简一下,我们发现,每次dp[i][j]
都只和dp[i][j-1]以及dp[i-1][1~j-1]中的最大值有关,因此我们不妨把dp化简,用两个数组表示,now[j]数组表
示当前a[j]在子序列中的最优值。pre[j]代表在上个序列中序列j最大值,在结合程序,也就比较好相通了。
相当于now维护dp[ i ] [ j ],而pre[j]维护的是dp[i-1][j] 。

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<string.h>
using namespace std;
const int maxn = 1e6+7;
const int INF = 0x3f3f3f3f;
int now[maxn];
int a[maxn];
int pre[maxn];
int main(){int m,n;while(~scanf("%d%d",&m,&n)){for (int i=1;i<=n;i++){scanf("%d",&a[i]);}int maxx;memset(now,0,sizeof(now));memset(pre,0,sizeof(pre));for (int i=1;i<=m;i++){maxx=-INF;for (int j=i;j<=n;j++){now[j]=max(now[j-1]+a[j],pre[j-1]+a[j]);pre[j-1]=maxx;if(now[j]>maxx)maxx=now[j];}}printf("%d\n",maxx);}return 0;
}
View Code

 

还做到一个很类似的题目:
把一段序列分成多个段,每个段最多k个,用每段的最大值替换每段里面的所有值。
其实也是一个DP:
maxx代表i-j内最大值,dp[j]=(dp[i-1]+maxx*(j-i+1),dp[j])
这样就非常简单的了,我们想想为什么,因为首先我们要确定一定要维护一个区间的最大值,因此也要
枚举区间,然后很容易写出转移方程。

B-不说了。。。

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int maxx = 1e6+7;
int a[maxx];
int n;
int main(){while(~scanf("%d",&n)){for (int i=1;i<=n;i++){scanf("%d",&a[i]);}sort(a+1,a+1+n);int ans=0;int last=a[1];int cnt=0;for (int i=1;i<=n;i++){if (last==a[i]){cnt++;}else {last=a[i];cnt=1;}if (ans<cnt){ans=a[i];}}printf("%d\n",ans);}return 0;
}
View Code

C-最长递增子序列问题翻版
之间把所有可能的情况加入,按照X坐标排序后,写出限制条件,求最长的可能。
dp[i]=max(dp[i],dp[j]+1) 其中j<i

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
struct node{int x,y,z;
}a[200];
int tot;
void join(int x,int y,int z){a[++tot].x=x;a[tot].y=y;a[tot].z=z;
}
bool cmp(node a,node b){return a.x<b.x;
}
int dp[80];
int main(){int n;int cas=1;while(~scanf("%d",&n)){if (n==0)break;int tmp[5];tot=0;memset(a,0,sizeof(a));for (int i=1;i<=n;i++){scanf("%d%d%d",&tmp[1],&tmp[2],&tmp[3]);join(tmp[1],tmp[2],tmp[3]);join(tmp[1],tmp[3],tmp[2]);join(tmp[2],tmp[1],tmp[3]);join(tmp[2],tmp[3],tmp[1]);join(tmp[3],tmp[1],tmp[2]);join(tmp[3],tmp[2],tmp[1]);}dp[0]=0;sort(a+1,a+1+tot,cmp);for (int i=1;i<=tot;i++){dp[i]=a[i].z;for (int j=i-1;j>=1;j--){if (a[j].x<a[i].x && a[j].y<a[i].y){dp[i]=max(dp[i],dp[j]+a[i].z);}}}int ans=0;for (int i=1;i<=tot;i++){ans=max(ans,dp[i]);}printf("Case %d: maximum height = %d\n",cas++,ans);}return 0;
}
View Code

D-经典的时间分配问题
状压DP
n种物品,枚举每一位的情况,用1-1<<n-1表示每一位是否存在,然后计算得分,然后维护dp[]最小,同时
保存路径,由于是求字典序最小,我们需要把枚举顺序反向,然后最后反向输出结果即可,因为我们记录的
是当前情况之前的情况,因此我们需要从最终情况开始往前找,找到前面即可,

#include<iostream>
#include<algorithm>
#include<string.h>
#include<stdio.h>
using namespace std;
const int maxx = 1<<15;
const int INF = 0x3f3f3f3f;
char name[20][120];
int die[20];
int fin[20];
int dp[maxx];//到达当前状态所需要的最小得分
int t[maxx];//做作业所需要花的时间
int pre[maxx];//到达某个状态所需要完成的课程
void output(int x){if (!x)return;output(x-(1<<pre[x]));printf("%s\n",name[pre[x]]);
}
int main(){int ca;scanf("%d",&ca);int n;while(ca--){scanf("%d",&n);memset(t,0,sizeof(t));int bit=1<<n;for (int i=0;i<n;i++){scanf("%s%d%d",name[i],&die[i],&fin[i]);}for (int i=1;i<bit;i++){dp[i]=INF;for (int j=n-1;j>=0;j--){int temp=1<<j;//第j位的情况if((i&temp)==0)continue;//这一位没有被选到int score=t[i-temp]+fin[j]-die[j];if (score<0)score=0;if (dp[i]>dp[i-temp]+score){//把分最小化dp[i]=dp[i-temp]+score;//更新为更小的t[i]=t[i-temp]+fin[j];//达到状态t[i]所花的时间pre[i]=j;//到达状态i的前驱
            }}}printf("%d\n",dp[bit-1]);output(bit-1);}return 0;
}
View Code

E Super Jumping! Jumping! Jumping!
翻版最长递增子序列

#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<string.h>
#define LL long long
using namespace std;
int a[1008];
LL dp[1008];
int main(){int n;while(~scanf("%d",&n) && n){for(int i=1;i<=n;i++){scanf("%d",&a[i]);}memset(dp,0,sizeof(dp));for (int i=1;i<=n;i++){dp[i]=a[i];for (int j=i-1;j>=1;j--){if (a[i]>a[j])dp[i]=max(dp[i],dp[j]+a[i]);}}LL ans=0;for (int i=1;i<=n;i++){ans=max(ans,dp[i]);}printf("%lld\n",ans);}return 0;
}
View Code

F HDU - 1114
完全背包+背包装满
由于是求最小值,初始化为边界值,然后判断是否仍是边界值,从而判断是否装满,
对于每个物品,枚举每个状态,并顺序枚举,这样每个状态可能用多次,从而达到完全背包的效果,反之
01背包则不行,必须反向枚举,每个状态都只能由一个状态构成。

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<string.h>
using namespace std;
int dp[10005];
int p[505];
int w[505];
const int INF = 0x3f3f3f3f;
int main(){int t;int E,F;scanf("%d",&t);while(t--){scanf("%d%d",&E,&F);int W=F-E;int n;scanf("%d",&n);memset(dp,0,sizeof(dp));for (int i=1;i<=n;i++){scanf("%d%d",&p[i],&w[i]);}for(int i=1;i<=W;i++){dp[i]=INF;}for (int i=1;i<=n;i++){for (int j=w[i];j<=W;j++){dp[j]=min(dp[j-w[i]]+p[i],dp[j]);}}if (dp[W]==INF){printf("This is impossible.\n");}else {printf("The minimum amount of money in the piggy-bank is %d.\n",dp[W]);}}return 0;
}
View Code

G HDU - 1176
数塔问题,从低到高反向求解

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int a[100015][12];
int dp[100015][12];
int main(){int n;while(scanf("%d",&n)!=EOF && n){int tmp1,tmp2;int t=0;memset(a,0,sizeof(a));memset(dp,0,sizeof(dp));for (int i=1;i<=n;i++){scanf("%d%d",&tmp1,&tmp2);a[tmp2][tmp1]++;t=max(tmp2,t);}for (int i=0;i<=10;i++){dp[t][i]=a[t][i];}for (int i=t-1;i>=0;i--){for (int j=0;j<=10;j++){if (j==0)dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+a[i][j];elsedp[i][j]=max(dp[i+1][j],max(dp[i+1][j-1],dp[i+1][j+1]))+a[i][j];}}printf("%d\n",dp[0][5]);}return 0;
}
View Code

H- HDU 1260
简单DP,给出买一张,买相邻两张的价格,可以选择买一张或者买两张相邻的。
转移方程
dp[i]=max(dp[i-1]+one[i],dp[i-2]+two[i-1])

#include<iostream>
#include<string.h>
#include<algorithm>
#include<stdio.h>
using namespace std;
int dp[2022];
int one[2022];
int two[2022];
const int INF = 0x3f3f3f3f;
int main(){int t;int n;scanf("%d",&t);while(t--){scanf("%d",&n);for (int i=1;i<=n;i++){scanf("%d",&one[i]);}for (int i=1;i<=n-1;i++){scanf("%d",&two[i]);}dp[1]=one[1];for (int i=2;i<=n;i++){dp[i]=min(dp[i-1]+one[i],dp[i-2]+two[i-1]);}int a=dp[n]/3600+8;int b=(dp[n]/60)%60;int c=dp[n]%60;if(a>12||(a==12&&(b>0||c>0)))printf("%.2d:%.2d:%.2d pm\n",a,b,c);elseprintf("%.2d:%.2d:%.2d am\n",a,b,c);}return 0;
}
View Code

I-HDU 1257
需要递减覆盖数目=最长递增子序列个数,因为没最长的递增子序列中的每个数,一定能把所有递减的序列
给覆盖掉。

#include<iostream>
#include<string.h>
#include<algorithm>
#include<stdio.h>
using namespace std;
int dp[10005];
int h[10005];
int main(){int n;while(~scanf("%d",&n)){for (int i=1;i<=n;i++){scanf("%d",&h[i]);}for (int i=1;i<=n;i++){dp[i]=1;for (int j=1;j<i;j++){if (h[i]>h[j]){dp[i]=max(dp[i],dp[j]+1);}}}int ans=0;for (int i=1;i<=n;i++){ans=max(ans,dp[i]);}printf("%d\n",ans);}return 0;
}
View Code

J-HDU - 1160
排序+最长递增子序列问题

#include<iostream>
#include<string.h>
#include<algorithm>
#include<stdio.h>
#include<stack>
using namespace std;
const int maxx = 1005;
struct node
{int w,s,id;
} a[maxx];
bool cmp1(node a,node b)
{return a.w<b.w;
}
int dp[1005];
int pre[1005];
int ans[1005];
int main()
{int n=1;while(~scanf("%d%d",&a[n].w,&a[n].s)){a[n].id=n;n++;}memset(pre,-1,sizeof(pre));memset(dp,0,sizeof(dp));sort(a+1,a+n,cmp1);for (int i=1; i<n; i++){dp[i]=1;for (int j=1; j<i; j++){if (a[j].w<a[i].w && a[j].s>a[i].s){if (dp[i]<dp[j]+1){dp[i]=dp[j]+1;pre[i]=j;}}}}
//    for (int i=1;i<n;i++){
//        cout<<a[i].w<<" "<<a[i].id<<endl;
//    }int maxx=0;int max_id;for (int i=1; i<n; i++){if (maxx<dp[i]){maxx=dp[i];//
            max_id=i;//记录下最后面的那个的位置
        }}int p=max_id;printf("%d\n",maxx);int cnt=1;while(p!=-1){ans[cnt++]=a[p].id;//printf("%d\n",a[p].id);p=pre[p];}for (int i=cnt-1;i>=1;i--){printf("%d\n",ans[i]);}return 0;
}
View Code

L-POJ - 1458
最长公共序列问题
用dp[i][j]表示,匹配到第i位和第j位时的最长递增公共序列的情况,
转移方程
第i位和第j位不匹配时
dp[i][j]=max(dp[i-1][j]),dp[i][j-1])
匹配时
dp[i][j]=dp[i-1][j-1]+1;

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
char a[10005];
char b[10005];
int dp[1005][1005];
int main(){while(~scanf("%s%s",&a,&b)){memset(dp,0,sizeof(dp));int n=strlen(a);int m=strlen(b);for (int i=1;i<=n;i++){for (int j=1;j<=m;j++){if (a[i-1]==b[j-1]){dp[i][j]=dp[i-1][j-1]+1;}else {dp[i][j]=max(dp[i-1][j],dp[i][j-1]);}}}printf("%d\n",dp[n][m]);}return 0;
}
View Code

M-POJ - 1661
高级版本数塔+排序
dp[i][0]表示跑到第i层的左边,left表示下面层数的左边跑上来,right是表示下面层的右边跑上来
dp[i][1]表示跑到第i层的右边
dp[i][0]=min(dp[left][0]+a[i].l-a[left].l,dp[left][1]+a[left].r-a[i].l);
dp[i][1]=min(dp[right][0]+a[i].r-a[right].l,dp[right][1]+a[right].r-a[i].r);

#include<iostream>
#include<string.h>
#include<algorithm>
#include<stdio.h>
#define LL long long
using namespace std;
struct node
{int l,r,h;
} a[1005];
bool cmp(node a,node b)
{return a.h<b.h;
}
int dp[1005][3];
int main()
{int t;scanf("%d",&t);int n,x,y,maxx;while(t--){scanf("%d%d%d%d",&n,&x,&y,&maxx);a[0].l=x;a[0].r=x;a[0].h=y;memset(dp,0,sizeof(dp));for (int i=1; i<=n; i++){scanf("%d%d%d",&a[i].l,&a[i].r,&a[i].h);dp[i][0]=0x3f3f3f3f;dp[i][1]=0x3f3f3f3f;}sort(a,a+1+n,cmp);for (int i=0;i<=n;i++){int left=-1;int right=-1;for (int j=i-1;j>=0;j--){if(left==-1 && maxx>=a[i].h-a[j].h && a[j].r>=a[i].l && a[j].l<=a[i].l){left=j;};if(right==-1 && maxx>=a[i].h-a[j].h && a[j].r>=a[i].r && a[j].l<=a[i].r){right=j;}}if (left!=-1){dp[i][0]=min(dp[left][0]+a[i].l-a[left].l,dp[left][1]+a[left].r-a[i].l);}else {if (a[i].h<=maxx)dp[i][0]=0;}if (right!=-1){dp[i][1]=min(dp[right][0]+a[i].r-a[right].l,dp[right][1]+a[right].r-a[i].r);}else{if (a[i].h<=maxx)dp[i][1]=0;}}printf("%d\n",dp[n][0]+y);}return 0;
}
View Code

N-POJ 2533
最长递增子序列

#include<iostream>
#include<algorithm>
#include<string.h>
#include<stdio.h>
using namespace std;
int a[1005];
int dp[1005];
int main(){int n;while(~scanf("%d",&n)){for (int i=1;i<=n;i++){scanf("%d",&a[i]);}memset(dp,0,sizeof(dp));for (int i=1;i<=n;i++){dp[i]=1;for (int j=1;j<i;j++){if (a[i]>a[j]){dp[i]=max(dp[i],dp[j]+1);}}}int ans=0;for (int i=1;i<=n;i++){ans=max(ans,dp[i]);}printf("%d\n",ans);}return 0;
}
View Code

O-POJ 3186
区间DP
dp[i][j]选到代表从左往右第i个和从右往左第j个
那么dp[i][j]=max(dp[i-1][j]+(i+j)*a[i],d[i][j-1]+(i+j)*a[n-j+1]);
最后找出在dp[i][n-1]中最大值的即可。

#include<iostream>
#include<algorithm>
#include<string.h>
#include<stdio.h>
using namespace std;
int a[2006];
int dp[2006][2006];
int main(){int n;while(~scanf("%d",&n)){for (int i=1;i<=n;i++){scanf("%d",&a[i]);}for (int i=0;i<=n;i++){for (int j=0;j+i<=n;j++){if (i>0 && j>0){dp[i][j]=max(dp[i-1][j]+(i+j)*a[i],dp[i][j-1]+(i+j)*a[n-j+1]);}else if (j>0){dp[i][j]=dp[i][j-1]+j*a[n-j+1];}else if (i>0){dp[i][j]=dp[i-1][j]+i*a[i];}}}int ans=0;for (int i=0;i<=n;i++){ans=max(dp[i][n-i],ans);}printf("%d\n",ans);}return 0;
}
View Code

P-HDU - 1078
DFS+DP
dp[i][j]=max(dp[i][j],dfs(i,j+k)+a[i][j]);
dp[i][j]=max(dp[i][j],dfs(i+k,j)+a[i][j]);

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define rep(i,j,k) for(int i=j;i<=k;i++)
using namespace std;
int a[115][115];
int dp[115][115];
int n,k;
int dfs(int x,int y)
{if (dp[x][y])return dp[x][y];dp[x][y]=a[x][y];for (int i=1;i<=k;i++){if (x+i<=n && a[x+i][y]>a[x][y]){dp[x][y]=max(dp[x][y],dfs(x+i,y)+a[x][y]);}if (x-i>=1 && a[x-i][y]>a[x][y]){dp[x][y]=max(dp[x][y],dfs(x-i,y)+a[x][y]);}if (y+i<=n && a[x][y+i]>a[x][y]){dp[x][y]=max(dp[x][y],dfs(x,y+i)+a[x][y]);}if (y-i>=1 && a[x][y-i]>a[x][y]){dp[x][y]=max(dp[x][y],dfs(x,y-i)+a[x][y]);}}return dp[x][y];
}
int main()
{while(~scanf("%d",&n)){memset(dp,0,sizeof(dp));scanf("%d",&k);if (n==-1 && k==-1)break;for (int i=1; i<=n; i++){for (int j=1; j<=n; j++){scanf("%d",&a[i][j]);}}printf("%d\n",dfs(1,1));}return 0;
}
View Code

Q-HDU - 2859
最大对称矩阵
从左下角往右上角
那么dp[i][j]=(dp[i-1][j+1]+1,dp[i][j])同时两边新增加的横和竖也要对称长度大于dp[i-1][j+1]
否则就等于MIN(对称长度,dp[i-1][j+1])

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int dp[1006][1006];
int n;
char a[1006][1006];
int main(){int n;while(~scanf("%d",&n) && n){for (int i=0;i<n;i++){scanf("%s",a[i]);}int ans=0;for (int i=0;i<n;i++){for (int j=0;j<n;j++){dp[i][j]=1;int x=i;int y=j;while(x>=0 && y<n && a[x][j]==a[i][y]){x--;y++;}int len=i-x;if (len>dp[i-1][j+1]){if (dp[i][j]<dp[i-1][j+1]+1)dp[i][j]=dp[i-1][j+1]+1;}else {dp[i][j]=len;}ans=max(ans,dp[i][j]);}}printf("%d\n",ans);}return 0;
}
View Code

R-POJ 3616
翻版最长递增子序列

#include<iostream>
#include<string.h>
#include<algorithm>
#include<stdio.h>
#define LL long long
using namespace std;
struct node{LL s;LL en;LL ef;
}a[2006];
bool cmp(node a,node b){if (a.s==b.s){return a.en<b.en;}else {return a.s<b.s;}
}
LL dp[2006];
int main(){int n,m,r;while(~scanf("%d%d%d",&n,&m,&r)){memset(dp,0,sizeof(dp));for (int i=1;i<=m;i++){scanf("%lld%lld%lld",&a[i].s,&a[i].en,&a[i].ef);}sort(a+1,a+1+m,cmp);for (int i=1;i<=m;i++){dp[i]=a[i].ef;for (int j=1;j<i;j++){//   cout<<a[i].s<<" "<<a[j].en<<endl;if(a[i].s>=a[j].en+r){dp[i]=max(dp[i],dp[j]+a[i].ef);}}}LL ans=0;for (int i=1;i<=m;i++){ans=max(ans,dp[i]);}printf("%lld\n",ans);}return 0;
}
View Code

S-POJ 3666

变成有序数列的最小代价
dp[i][j]表示选到a[i]的时候,把a[i]变成数列中第j小的数。
首先我们容易写出基本的DP
dp[i][j]=abs(j-w[i])+min(dp[i-1][k]);(k<=j)
表示选到i时,以j为最大值。
他是由j-w[i]以及dp[i-1][k]中以k为最小值(k<=j)传来的
那么我只需了,遍历时维护dp[i-1][k]=min即可
方程也就写成了dp[i][j]=abs(a[i]-j)+min;
最后把j离散化即可
dp[i][j]=abs(a[i]-b[j])+min.其中b是a排序以后的值。
相当于dp[i][j]是把a[i]变成b[j]再加上以前dp[i-1][k]的最小值。

#include<iostream>
#include<algorithm>
#include<string.h>
#include<stdio.h>
using namespace std;
const int N = 2005;
int n;
int a[N],b[N];
long long dp[N][N];
int main(){while(~scanf("%d",&n)){for (int i=1;i<=n;i++){scanf("%d",&a[i]);b[i]=a[i];}sort(b+1,b+1+n);for (int i=1;i<=n;i++){long long mn=dp[i-1][1];for (int j=1;j<=n;j++){mn=min(mn,dp[i-1][j]);dp[i][j]=abs(a[i]-b[j])+mn;}}long long ans=dp[n][1];for (int i=1;i<=n;i++){ans=min(ans,dp[n][i]);}printf("%lld\n",ans);}return 0;
}
View Code

转载于:.html

更多推荐

kuangbin 基础DP集合

本文发布于:2024-03-23 14:34:19,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1739309.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:基础   kuangbin   DP

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!