kuangbin 基础DP集合

时间:2023-03-09 17:13:25
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+;
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=;i<=n;i++){
scanf("%d",&a[i]);
}
int maxx;
memset(now,,sizeof(now));
memset(pre,,sizeof(pre));
for (int i=;i<=m;i++){
maxx=-INF;
for (int j=i;j<=n;j++){
now[j]=max(now[j-]+a[j],pre[j-]+a[j]);
pre[j-]=maxx;
if(now[j]>maxx)maxx=now[j];
}
}
printf("%d\n",maxx);
}
return ;
}

还做到一个很类似的题目:
把一段序列分成多个段,每个段最多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+;
int a[maxx];
int n;
int main(){
while(~scanf("%d",&n)){
for (int i=;i<=n;i++){
scanf("%d",&a[i]);
}
sort(a+,a++n);
int ans=;
int last=a[];
int cnt=;
for (int i=;i<=n;i++){
if (last==a[i]){
cnt++;
}else {
last=a[i];
cnt=;
}
if (ans<cnt){
ans=a[i];
}
}
printf("%d\n",ans);
}
return ;
}

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[];
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[];
int main(){
int n;
int cas=;
while(~scanf("%d",&n)){
if (n==)break;
int tmp[];
tot=;
memset(a,,sizeof(a));
for (int i=;i<=n;i++){
scanf("%d%d%d",&tmp[],&tmp[],&tmp[]);
join(tmp[],tmp[],tmp[]);
join(tmp[],tmp[],tmp[]);
join(tmp[],tmp[],tmp[]);
join(tmp[],tmp[],tmp[]);
join(tmp[],tmp[],tmp[]);
join(tmp[],tmp[],tmp[]);
}
dp[]=;
sort(a+,a++tot,cmp);
for (int i=;i<=tot;i++){
dp[i]=a[i].z;
for (int j=i-;j>=;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=;
for (int i=;i<=tot;i++){
ans=max(ans,dp[i]);
}
printf("Case %d: maximum height = %d\n",cas++,ans);
}
return ;
}

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

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

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

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

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

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<string.h>
using namespace std;
int dp[];
int p[];
int w[];
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,,sizeof(dp));
for (int i=;i<=n;i++){
scanf("%d%d",&p[i],&w[i]);
}
for(int i=;i<=W;i++){
dp[i]=INF;
}
for (int i=;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 ;
}

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

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

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[];
int one[];
int two[];
const int INF = 0x3f3f3f3f;
int main(){
int t;
int n;
scanf("%d",&t);
while(t--){
scanf("%d",&n);
for (int i=;i<=n;i++){
scanf("%d",&one[i]);
}
for (int i=;i<=n-;i++){
scanf("%d",&two[i]);
}
dp[]=one[];
for (int i=;i<=n;i++){
dp[i]=min(dp[i-]+one[i],dp[i-]+two[i-]);
}
int a=dp[n]/+;
int b=(dp[n]/)%;
int c=dp[n]%;
if(a>||(a==&&(b>||c>)))
printf("%.2d:%.2d:%.2d pm\n",a,b,c);
else
printf("%.2d:%.2d:%.2d am\n",a,b,c);
}
return ;
}

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

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

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

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

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[];
char b[];
int dp[][];
int main(){
while(~scanf("%s%s",&a,&b)){
memset(dp,,sizeof(dp));
int n=strlen(a);
int m=strlen(b);
for (int i=;i<=n;i++){
for (int j=;j<=m;j++){
if (a[i-]==b[j-]){
dp[i][j]=dp[i-][j-]+;
}else {
dp[i][j]=max(dp[i-][j],dp[i][j-]);
}
}
}
printf("%d\n",dp[n][m]);
}
return ;
}

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[];
bool cmp(node a,node b)
{
return a.h<b.h;
}
int dp[][];
int main()
{
int t;
scanf("%d",&t);
int n,x,y,maxx;
while(t--)
{
scanf("%d%d%d%d",&n,&x,&y,&maxx);
a[].l=x;
a[].r=x;
a[].h=y;
memset(dp,,sizeof(dp));
for (int i=; i<=n; i++)
{
scanf("%d%d%d",&a[i].l,&a[i].r,&a[i].h);
dp[i][]=0x3f3f3f3f;
dp[i][]=0x3f3f3f3f;
}
sort(a,a++n,cmp);
for (int i=;i<=n;i++){
int left=-;
int right=-;
for (int j=i-;j>=;j--){
if(left==- && maxx>=a[i].h-a[j].h && a[j].r>=a[i].l && a[j].l<=a[i].l){
left=j;
};
if(right==- && maxx>=a[i].h-a[j].h && a[j].r>=a[i].r && a[j].l<=a[i].r){
right=j;
}
}
if (left!=-){
dp[i][]=min(dp[left][]+a[i].l-a[left].l,dp[left][]+a[left].r-a[i].l);
}else {
if (a[i].h<=maxx)dp[i][]=;
}
if (right!=-){
dp[i][]=min(dp[right][]+a[i].r-a[right].l,dp[right][]+a[right].r-a[i].r);
}else{
if (a[i].h<=maxx)dp[i][]=;
}
}
printf("%d\n",dp[n][]+y);
}
return ;
}

N-POJ 2533
最长递增子序列

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

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[];
int dp[][];
int main(){
int n;
while(~scanf("%d",&n)){
for (int i=;i<=n;i++){
scanf("%d",&a[i]);
}
for (int i=;i<=n;i++){
for (int j=;j+i<=n;j++){
if (i> && j>){
dp[i][j]=max(dp[i-][j]+(i+j)*a[i],dp[i][j-]+(i+j)*a[n-j+]);
}else if (j>){
dp[i][j]=dp[i][j-]+j*a[n-j+];
}else if (i>){
dp[i][j]=dp[i-][j]+i*a[i];
}
}
}
int ans=;
for (int i=;i<=n;i++){
ans=max(dp[i][n-i],ans);
}
printf("%d\n",ans);
}
return ;
}

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[][];
int dp[][];
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=;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>= && 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>= && 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,,sizeof(dp));
scanf("%d",&k);
if (n==- && k==-)break;
for (int i=; i<=n; i++)
{
for (int j=; j<=n; j++)
{
scanf("%d",&a[i][j]);
}
}
printf("%d\n",dfs(,));
}
return ;
}

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[][];
int n;
char a[][];
int main(){
int n;
while(~scanf("%d",&n) && n){ for (int i=;i<n;i++){
scanf("%s",a[i]);
}
int ans=;
for (int i=;i<n;i++){
for (int j=;j<n;j++){
dp[i][j]=;
int x=i;
int y=j;
while(x>= && y<n && a[x][j]==a[i][y]){
x--;
y++;
}
int len=i-x;
if (len>dp[i-][j+]){
if (dp[i][j]<dp[i-][j+]+)
dp[i][j]=dp[i-][j+]+;
}else {
dp[i][j]=len;
}
ans=max(ans,dp[i][j]);
}
}
printf("%d\n",ans);
}
return ;
}

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[];
bool cmp(node a,node b){
if (a.s==b.s){
return a.en<b.en;
}else {
return a.s<b.s;
}
}
LL dp[];
int main(){
int n,m,r;
while(~scanf("%d%d%d",&n,&m,&r)){
memset(dp,,sizeof(dp));
for (int i=;i<=m;i++){
scanf("%lld%lld%lld",&a[i].s,&a[i].en,&a[i].ef);
}
sort(a+,a++m,cmp);
for (int i=;i<=m;i++){
dp[i]=a[i].ef;
for (int j=;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=;
for (int i=;i<=m;i++){
ans=max(ans,dp[i]);
}
printf("%lld\n",ans);
}
return ;
}

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 = ;
int n;
int a[N],b[N];
long long dp[N][N];
int main(){
while(~scanf("%d",&n)){ for (int i=;i<=n;i++){
scanf("%d",&a[i]);
b[i]=a[i];
}
sort(b+,b++n);
for (int i=;i<=n;i++){
long long mn=dp[i-][];
for (int j=;j<=n;j++)
{
mn=min(mn,dp[i-][j]);
dp[i][j]=abs(a[i]-b[j])+mn;
}
}
long long ans=dp[n][];
for (int i=;i<=n;i++){
ans=min(ans,dp[n][i]);
}
printf("%lld\n",ans);
}
return ;
}