动态规划学习《算法竞赛入门经典》

数字三角形

d(i,j)=a(i,j)+max{d(i+1,j),d(i,j+1)}

d(i,j)是指从i层第j个元素出发的的最大和

int solve()
{
  for(int i=1;i<=n;i++)  d[n][i]=a[n][i];//初值

  //变量枚举
  for(int i=n-1;i>=1;i--)
    for(int j=1;j<=i;j++)
    d[i][j]=max(d[i+1][j],d[i][j+1])+a[i][j];
}

嵌套矩形

模型:不固定起点与终点的DAG(有向无环图)模型
先根据关系建有向图(G[i][j]=1,表示矩形i可以嵌套在矩形j)

d(i)=max{d(i),d(j)+1|(i,j)ϵE}        d(i)i

要用记忆化搜索,其实和DFS一样只是多了一个记忆化的优化,避免了子问题的重复计算。
为什么不用递推:递推需要有序的更新值,然而这道题的顺序不容易确定。
例题: 矩形嵌套

描述:有n个矩形,每个矩形可以用a,b来描述,表示长和宽。矩形X(a,b)可以嵌套在矩形Y(c,d)中当且仅当a < c,b < d或者b < c,a < d(相当于旋转X90度)。例如(1,5)可以嵌套在(6,2)内,但不能嵌套在(3,4)中。你的任务是选出尽可能多的矩形排成一行,使得除最后一个外,每一个矩形都可以嵌套在下一个矩形内。

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef long long ll;
#define INF (1<<31)-1
using namespace std;
const int maxn=1005;
int n;
struct node
{
    int x,y;
}tri[maxn];
int G[maxn][maxn];//存储DAG
int dp[maxn];
bool check(struct node a,struct node b)
{ //嵌套判断
        return ((a.xint DP(int i)
{
    int& ans=dp[i];
    if(ans>0) return ans;
    ans=1;
    for(int j=1;j<=n;j++)
        if(G[i][j])  ans=max(ans,DP(j)+1);
    return ans;
}
int main()
{
    int N;
    cin>>N;
    while(N--)
    {
        memset(G,0,sizeof(G));
        memset(dp,0,sizeof(dp));
        cin>>n;
        for(int i=1;i<=n;i++)
            cin>>tri[i].x>>tri[i].y;
         for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                if(check(tri[i],tri[j]))
                      G[i][j]=1;
        int ans=-1;
        for(int i=1;i<=n;i++)
            DP(i);
        for(int i=1;i<=n;i++)
            ans=max(dp[i],ans);
        cout<return 0;
}


硬币问题

问题描述:有n中硬币,面值分别为V1,V2……Vn,每种都有无限多。给定非负整数S,可以选用多少个硬币,使得面值之和恰为S?输出硬币数目的最小值和最大值。

模型是固定终点的最长路和最短路

dii0,di=max{d(iV[j])+1|}di=min{d(iV[j])+1}

可以发现这个题的参数i是有序的,因此可以选用递推算法,且递推方向是逆序。
注意这种固定终点的路径长度问题:有可能无法到达终点出现无解状况,判断是否有解就看起点的值是否发生变化,若没有发生变化就是无解。

//设置合理的边界条件
minv[0]=maxv[0]=0;
for(int i=1;i<=S;i++){
   minv[i]=INF; maxv[i]=-INF;
}
for(int i=1;i<=S;i++)
  for(int j=1;j<=n;j++)
  if(i>=V[j]){
    minv[i]=min(minv[i],minv[i-V[j]]+1);
    maxv[i]=max(maxv[i],maxv[i-V[j]]+1);
  }
  if(minv[0]==INF||maxv[0]==-INF) printf("-1");//无解
  else   printf("%d %d",minv[S],maxv[S]);

地铁里的间谍

分析:题目有两个关键量:时间和地点,按照题目描述我们可以设置d(i,j)表示时刻i,在车站j,最少还要等待多长时间。很明显每一个状态都有三个决策

  1. :dp[i][j]=dp[i+1][j]+1
  2. :dp[i][j]=min(dp[i][j],dp[i+t[j] ] [j+1])
  3. dp[i][j]=min(dp[i][j],dp[i+t[j1] ] [j1])

t[j]jj+1
接下来确定递推方向直观的我们要求的是dp[0][1] 而且从状态转移方程中我们也可以看出是逆着时间的顺序递推的
那么边界就可以确定了
d(T,n)=0;d(T,i)=INF;

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef long long ll;
#define INF (1<<20)-1
using namespace std;
int t[55];//各站点行驶时间
int d1[55],d2[55];//记录向左出发的车的时间,向右出发车的时间
int dp[205][55];
int has_train[205][55][2];//时刻T在站点i是否有向左开,或向右开的车
              int n,T;
int main()
{
    int Kase=0;
    while(cin>>n&&n) {
        memset(has_train,0,sizeof(has_train));
        cin>>T;
        for(int i=1; i<=n-1; i++)
            cin>>t[i];//各个站点行驶时间
        int m1,m2;
        cin>>m1;
        for(int i=1; i<=m1; i++) { //向右出发
            int time=0;
            cin>>d1[i];
            has_train[d1[i]][1][0]=1;//第一个站点是否有车看车的出发时间
            for(int j=1; j//处理has_train数组
                time+=t[j];
                has_train[time+d1[i]][j+1][0]=1;
            }
        }
        cin>>m2;
        for(int i=1; i<=m2; i++) { //向左出发
            int time=0;
            cin>>d2[i];
            has_train[d2[i]][n][1]=1;//第n个站点是否有车看车的出发时间
            for(int j=n-1;j>1;j--) {
                //处理has_train数组
                time+=t[j];
                has_train[time+d2[i]][j][1]=1;//注意车站编号从左向右递增
            }
        }

        //设置边界
        dp[T][n]=0;
        for(int i=1; i<=n-1; i++) //由于最终要求的是dp[0][1];递推方向是沿着时间逆序,故边界设置如此
            dp[T][i]=INF;

        for(int i=T-1; i>=0; i--)
            for(int j=1; j<=n; j++) {
                dp[i][j]=dp[i+1][j]+1;//没有车子可乘等待1
                if(j0]&&i+t[j]<=T)
                    dp[i][j]=min(dp[i][j],dp[i+t[j]][j+1]);//搭乘向右的车
                if(j>1&&has_train[i][j][1]&&i+t[j-1]<=T)
                    dp[i][j]=min(dp[i][j],dp[i+t[j-1]][j-1]);//搭乘向左的车
            }

            cout<<"Case Number "<<++Kase<<": ";
            if(dp[0][1]>=INF) cout<<"impossible"<else cout<0][1]<return 0;
}


巴比伦塔

题意:有n( n30 )种立方体,每种都有无穷个,要去选一些立方体摞成一根尽量高的柱子(可以自行选择哪一条边坐高),使得每个立方体的地面长宽分别严格小于它下方立方体的地面长宽。
分析:类似与嵌套矩形属于DAG最长路的模型。需要注意的是每一组数据a,b,c可能组成三种立方体。
diidi=max{(d(j)+h(i)|(i,j)E}

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef long long ll;
#define INF (1<<31)-1
using namespace std;
const int maxn=30*3+3;

struct node {
    int x,y,z;
} blo[maxn];
int dp[maxn];
int n;
int check(struct node a,struct node b)
{
    //a是否可以堆积在b上
    if((a.x>b.x&&a.y>b.y)||(a.y>b.x&&a.x>b.y))
        return 1;
    return 0;
}
int DP(int a)
{
    if(dp[a]>0) return dp[a];
    dp[a]=blo[a].z;
    for(int i=1; i<=3*n; i++) {

        if(check(blo[a],blo[i]))
            dp[a]=max(dp[a],DP(i)+blo[a].z);
        }
    return dp[a];
}

int main()
{
    int Kase=0;
    while(cin>>n&&n) {
        memset(dp,0,sizeof(dp));
        int cur=1;
        for(int i=1; i<=n; i++) {
            int a,b,c;
            cin>>a>>b>>c;
            blo[cur].x=a;
            blo[cur].y=b;
            blo[cur++].z=c;
            blo[cur].x=a;
            blo[cur].y=c;
            blo[cur++].z=b;
            blo[cur].x=b;
            blo[cur].y=c;
            blo[cur++].z=a;
        }
        for(int i=1; i<=3*n; i++)
            DP(i);
        int mmax=-1;
        for(int i=1; i<=3*n; i++)
            mmax=max(mmax,dp[i]);
        printf("Case %d: maximum height = %d\n",++Kase,mmax);
    }
    return 0;
}


单向 DSP

题意:给一个n行m列( m10,n100 )的整数矩阵,从第一列任何一个位置出发每次往右,右上或右下走一格,最终走到最后一列。要求经过的整数之和最小。整个矩阵是环行的,即第一行的上一行是最后一行,最后一行的下一行是第一行。输出路径上每列的行号。多解时,输出字典序最小的。

模型:多阶段决策的特征是要将问题按照某种次序划分成若干相互联系的阶段,以便按照一定次序去求解。

思路:这是个明显的多阶段决策问题。
di,jijdij=max{d(i,j1),d(i1,j1),d(i+1,j+1)}+a[i][j]; 递推方向显然是逆推.。

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef long long ll;
#define INF (1<<30)-1
using namespace std;
const int maxn=105;
int G[maxn][maxn];
int d[maxn][maxn];
int nnext[maxn][maxn];
int n,m;
int main()
{
    while(cin>>m>>n) {

        for(int i=0; ifor(int j=0; jcin>>G[i][j];
        int ans=INF;
       int  first=0;
        for(int j=n-1; j>=0; j--) {
            for(int i=0; iif(j==n-1) d[i][j]=G[i][j];//设置边界
                else {
                    int rows[3]= {i,i-1,i+1}; //这是之前不知道的用法
                    if(i==0) rows[1]=m-1; //设置环行  第0行的上一行是m-1
                    if(i==m-1) rows[2]=0;   //第m-1行的下一行是第0行
                    sort(rows,rows+3);//保持字典序最小
                    d[i][j]=INF;
                    for(int k=0; k<3; k++) {
                        int v=d[rows[k]][j+1]+G[i][j];
                        if(v//记录当前位置的下一个位置的行
                        }
                    }
                }

                if(j==0&&d[i][j]//特别找出第一列最优的行号
                    first=i;
                }
            }

        }
        printf("%d",first+1);
        for(int i=nnext[first][0],j=1;j//更新输出下一个行号
            printf(" %d",i+1);
        printf("\n%d\n",ans);
    }
    return 0;
}


你可能感兴趣的:(动态规划)