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; }
还做到一个很类似的题目:
把一段序列分成多个段,每个段最多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; }
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; }
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; }
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; }
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; }
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]; else dp[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; }
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); else printf("%.2d:%.2d:%.2d am\n",a,b,c); } return 0; }
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; }
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; }
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; }
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; }
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; }
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; }
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; }
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; }
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; }
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; }