DP入门练习

一、

动态规划(Dynamic Programming,简称 DP)是一种通过分解复杂问题为重叠子问题,并存储子问题的解(避免重复计算) 来高效求解问题的算法思想。它的核心是 “以空间换时间”,适用于具有重叠子问题最优子结构性质的问题。

  1. 重叠子问题:问题可以分解为多个重复出现的子问题(例如:计算斐波那契数列时,fib(5)需要fib(4)fib(3),而fib(4)又需要fib(3)fib(2),其中fib(3)被重复计算)。

  2. 最优子结构:问题的最优解包含子问题的最优解(例如:求 “从 A 到 B 的最短路径”,如果路径 A→C→B 是最优解,那么 A→C 和 C→B 也必须是各自的最短路径)。

二、步骤

  1. 确定状态:用一个变量(或多个变量)描述问题在某一阶段的 “状态”(即子问题的解)。
  2. 推导状态转移方程:描述不同状态之间的关系(即如何从已知子问题的解推出当前问题的解)。
  3. 确定初始条件:最基础的子问题(无法再分解的子问题)的解。
  4. 计算顺序:通常采用 “自底向上” 的顺序(从基础子问题开始,逐步计算到目标问题),或 “自顶向下 + 记忆化”(递归 + 缓存)。

三、两种实现方式

  1. 自底向上(递推)
    • 从最小的子问题开始计算,逐步推导到目标问题(如斐波那契、爬楼梯)。
    • 优点:无递归栈开销,空间可优化,效率高。
  2. 自顶向下(递归 + 记忆化)
    • 直接递归解决目标问题,用哈希表 / 数组缓存子问题的解(避免重复计算)。
    • 优点:思路更直观,适合复杂问题。

"递归" 是自顶向下, "递"是分解问题(自顶向下), "归"是计算求解(自底向上); 能不能只有"归"呢? 那就自底向上的 "递推"

递推公式= dfs向下递归的公式, 递推数组初始值= dfs 递归边界(递归搜索树的叶子)

ps:

剪枝 和 记忆化搜索 都是优化dfs的两种方式, 但是要实现剪枝, 就要尽可能多的传递参数(根据参数剪);

但是记忆化搜索, 参数越少越好, 因为记忆化数组需要根据参数来进行映射,(5 个参数可能就需要5维度数组), 这不利于实现.

因此, 在记忆化搜索中, 不应该把没有影响到边界的参数传进来

acwing 5292.跳台阶

一个楼梯共有 n 级台阶,每次可以走一级或两级,问从第 0 级台阶走到第 n 级台阶一共有多少种方案。

输入格式

共一行,包含一个整数 n。

输出格式

共一行,包含一个整数,表示方案数。

数据范围

1≤n≤15

纯dfs暴搜有两种, 一种是从下到上, 一种是从上到下

1. 从下到上:

void solve(){
    int n;cin>>n;
    int ans=0;
    auto f=[&](auto self ,int x)->void{
        if(x>n)return;
        if(x==n){
            ans++;
        }else{
            self(self,x+1);
            self(self,x+2);// 搜索树2个起点? 并非, 其实是0开始
        }
    };
    f(f,0);// 0开始
    cout<<ans<<endl;
}

实现记忆化递归 (记忆化一定要递归):

void solve(){
	int n;cin>>n;
    vector<int> dp(n+1);
    auto f=[&](auto self, int x)->int{
        if(x>n)return 0;  // 代表这条路径不合法
        if(x==n)return 1; // 这条路径合法
        if(dp[x])return dp[x]; // 记录: 第x层到第n层有多少条路径
        int sum = self(self,x+1)+self(self,x+2);// 因为这里要有返回值,所以后面记得return;
        dp[x]=sum;
        return sum;//
    };
    f(f,0);
    cout<<dp[0]<<endl;

}

直接递推:

刚刚是从 0 搜到 n, 现在要从 n 推回 0

void solve(){
	int n;cin>>n;
    vector<int> dp(n+1);

    dp[n]=1;dp[n-1]=1;  // 记录: 第x层到第n层有多少条路径, 所以n层是唯一的, n-1层也是唯一的
    for(int i=n-2;i>=0;i--){
        dp[i]=dp[i+1]+dp[i+2];
    }
    cout<<dp[0]<<endl;
}

2. 从上到下:

void solve(){
    int n;cin>>n;
    auto dfs=[&](auto self , int x)->int{
        if(x==1)return 1;
        if(x==2)return 2;
        return self(self,x-1)+self(self,x-2);
    };
    cout<<dfs(dfs,n)<<endl; // n开始
}

实现记忆化递归搜索

 void solve(){
    int n;cin>>n;
    vector<int> dp(n+1);
    auto dfs=[&](auto self , int x)->int{
        if(dp[x])return dp[x];// 如果有记录的话,直接使用记录
        int sum=0;
        if(x==1)sum=1;
        else if(x==2)sum=2;
        else sum=self(self,x-1)+self(self,x-2);//用一个sum 来记录当前层的结果
        dp[x]=sum;// 第x层的答案为sum, 作为一个映射,用dp数组存下来
        return sum;
    };
    cout<<dfs(dfs,n)<<endl;    
}

所以只是存了一下每次递归对应的结果

根据记忆化搜索, 可以很自然地写出递推公式, 这就是最终形态了. (当然这还能用四个变量进一步优化空间, 但这是后话了)

void solve(){
    int n;cin>>n;
    vector<int> dp(100);
    dp[0]=1;dp[1]=1;
   	if(n>1){
        rep(i,2,n){
            dp[i]=dp[i-1]+dp[i-2];
        }
    }
    cout<<dp[n]<<endl;
}

Acwing 1049. 大盗阿福

题目描述
阿福是一名经验丰富的大盗。趁着月黑风高,阿福打算今晚洗劫一条街上的店铺。这条街上一共有 N 家店铺,每家店中都有一些现金。阿福事先调查得知,只有当他同时洗劫了两家相邻的店铺时,街上的报警系统才会启动,然后警察就会蜂拥而至。作为一向谨慎作案的大盗,阿福不愿意冒着被警察追捕的风险行窃。

他想知道,在不惊动警察的情况下,他今晚最多可以得到多少现金?

输入格式
输入的第一行是一个整数 T ,表示一共有 T 组数据。

接下来的每组数据,第一行是一个整数 N ,表示一共有 N 家店铺。

第二行是 N 个被空格分开的正整数,表示每一家店铺中的现金数量。

每家店铺中的现金数量均不超过 1000 。

输出格式
对于每组数据,输出一行。该行包含一个整数,表示阿福在不惊动警察的情况下可以得到的现金数量。

数据范围
1 ≤ T ≤ 50 , 1≤N≤10^5

DFS暴搜

void solve(){
    int n;cin>>n;
    vector<int> a(n+1);
    rep(i,1,n)cin>>a[i];
    int ans=0;
    vector<int> vis(n+2);
    auto f=[&](auto self,int x, int num)->void{
        if(x==n+1){
            ans=max(ans,num);
        }else{
            if(vis[x]==0){
                vis[x]=1;vis[x+1]=1;
                self(self,x+1,num+a[x]);
                vis[x]=0;vis[x+1]=0;
                self(self,x+1,num);
            }else{
                self(self,x+1,num);
            }
        }
    };
    f(f,1,0);
    cout<<ans<<endl;
}

注意到这里的num 参数不是影响边界的条件, 理论上来说适合剪枝优化 而不是 记忆化或者说dp

那不妨先剪个枝吧, 可以是当前的num加上后续所有的 a[x]如果还小于ans就直接return, 但是这样感觉优化不了多少, 我暂时想不到更好的剪枝了

首先可以优化掉vis数组, 转变为如果选了当前的x, 就考虑x+2,否则考虑x+1, 那搜索树也很直观了, 先画出来吧,

auto f=[&](auto self,int x, int num)->void{
    if(x>n){// x>n
        ans=max(ans,num);
    }else{
            self(self,x+2,num+a[x]);
            self(self,x+1,num);
    }
};

能不能再优化掉这个不决定边界的参数num ?

现在就先 "递" 搜索选择方案, 然后 " 归 "计算局部最优解 ?

现在用dp数组记录到第x个时, 选或不选 a[x] 的最优解, 最优解表示为 max( f(x+1), a[x] + f(x+2) ) 这个要根据搜索树写出来

最小子问题呢? 应该是 到第n层 的时候 , 不能再往下了, 那就把它选进来 或者到第 n-1 的时候, 肯定不能选n (相邻) 或者n+1了(n+1根本不存在)

void solve(){
    int n;cin>>n;
    vector<int> a(n+1);
    rep(i,1,n)cin>>a[i];
    int ans=0;
    vector<int> dp((int)1e5+5);
    auto f=[&](auto self,int x)->int{
        if(x>n)return 0;//可有可无好像是
        if(dp[x])return dp[x];
        if(x==n)return a[n];
        if(x==n-1)return max(a[n-1],a[n]); 
        // 选的话就是加上a[n-1],然后不能再选下一个a[n]了(至于a[n-2]呢?如果选了a[n-2],根本就不会进入到n-1这层考虑)
        
        int sum = max(self(self,x+1), self(self,x+2)+a[x] ); // 当前层的最优解记为sum;
        dp[x]=sum;
        return sum;
    };
    f(f,1);
    cout<<dp[1]<<endl;
}

然后写递推

void solve(){
    int n;cin>>n;
    vector<int> a(n+1);
    rep(i,1,n)cin>>a[i];
    
    vector<int> dp((int)1e5+5);
    
    dp[n]=a[n];dp[n-1]=max(a[n-1],a[n]);
    
    for(int i=n-2;i>=1;i--){
        dp[i]=max(dp[i+1],dp[i+2]+a[i]);
    }
    cout<<dp[1]<<endl;
}

P1216 [IOI 1994] 数字三角形 Number Triangles

题目描述

观察下面的数字金字塔。

写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。

img

在上面的样例中,从 7→3→8→7→5 的路径产生了最大权值。

输入格式

第一个行一个正整数 r,表示行的数目。1≤r≤1000

后面每行为这个数字金字塔特定行包含的整数 [0,100] 。

输出格式

单独的一行,包含那个可能得到的最大的和。

输入输出样例

输入 #1复制

5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5 

输出 #1复制

30

和上一题类似,对于每个节点, 都记录走这条节点往下的最大值

那么, 走当前节点(x,y)的 最优解(最大值) 就是 dp[x][y] = a[x][y] + max( dp[x+1][y] , dp[x+1][y+1] )

最小子问题就是最下面那层 dp [n] [i] = a [n] [i] ;

然后就从下往上递推回去

void solve(){
    int n;cin>>n;
    vector<vector<int>> a(1001,vector<int>(1001));
    vector<vector<int>> dp(1001,vector<int>(1001));

    rep(i,1,n){
        rep(j,1,i){
            cin>>a[i][j];
        }
    }
    rep(i,1,n)dp[n][i]=a[n][i];
    for(int i=n-1;i>=1;i--){
        for(int j=1;j<=i;j++){
            dp[i][j]=a[i][j]+max(dp[i+1][j],dp[i+1][j+1]);
        }
    }
    cout<<dp[1][1]<<endl;

}

那现在能不能还原递归搜索树呢?

auto f=[&](auto self, int x,int y)->int{
        if(dp[x][y])return dp[x][y];// dp数组是二维的,显然需要两个决定边界的参数
        int sum = a[x][y]+max(self(self,x+1,y),self(self,x+1,y+1));
        dp[x][y]=sum; // 别忘了记录
        return sum;
    };
f(f,1,1);

但是 RE 了, 所以之后的这种题用递推来做吧...

Acwing 01背包问题

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式

第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式

输出一个整数,表示最大价值。

数据范围

0<N,V≤1000
0<vi,wi≤1000

输入样例

4 5
1 2
2 4
3 4
4 5

输出样例:

8

先不管什么记忆化或者dp了, 从朴素的来看, 这就是一个指数型枚举

然后就可以顺手写个暴搜

void solve(){
    int n,m;cin>>n>>m;
    vector<pair<int,int>> a(n+1);
    rep(i,1,n){
        int v,w;cin>>v>>w;
        a[i].first=v;a[i].second=w;
    }
    int ans=0;
    auto f=[&](auto self,int x, int sp ,int sum)->void{
        if(x>n){
            ans=max(ans,sum);
        } else{
            // if(sp>=a[x].first){
            //     self(self,x+1,sp-a[x].first,sum+a[x].second);
            //     self(self,x+1,sp,sum);
            // }else{
            //     self(self,x+1,sp,sum);
            // }
                //这写的太复杂了, 这样就好↓
            if(sp>=a[x].first){
                self(self,x+1,sp-a[x].first,sum+a[x].second);
            }
            self(self,x+1,sp,sum);//无论如何都保留不选
        } 
       
    };
    f(f,1,m,0);
    cout<<ans<<endl;
}

选到第 x 件物品和背包的剩余容量 sp 都会影响搜索树的边界, 那 dp 数组自然就要开二维的,

(记忆化一定是有用的, 比如通过前面的某一些选法, 到了这一件物品或者说这一层, 都剩余这么多空间 , 这么多空间就有一个使价值最大化的解(利用方案, 如果之前搜过, 那就直接使用 ) , 然后利用这个后面的最优解, 去尝试(chose or not )和计算上面一层的解. ) (这里前面描述的是"递", 后面遇到已知最优解是"归")

x和 sp 肯定要在搜索树中保留, 然后优化掉 sum, 也就是: 先只走路, 画搜索树

对于第一个点(1,m), f(f,1,m,0);也是从(1,m)开始搜嘛,这很合理

这个状态表示: 对第一件物品, 还剩余m 的空间

那么如果取第一件物品 搜索树就会有 : (1,m) → ( 2 , m - a[1].v )

如果不选的话就是 : (1,m) → ( 2 , m )

那么当前层的答案就是: max( f(2,m), a[1].w+f(2,m-a[1].v) ) ?

dp[ x ] [ y ] 存的是当前最优解

差点忘记分析搜索树底了...

如果到了最底层还有剩余足够的空间, 那一定要把这个选上

发现都只要考虑下一层, 那就只考虑x==n的时候了

void solve(){
    int n,m;cin>>n>>m;

    vector<pair<int,int>> a(n+1);

    rep(i,1,n){
        int v,w;cin>>v>>w;
        a[i].first=v;a[i].second=w;
    }
    vector<vector<int>> dp(n+1,vector<int> (m+1,-1));// 
    auto f=[&](auto self,int x, int sp )->int{
        // if(sp<0)return -a[x-1].second;// 返回负数, 不能返回0 ? (因为返回的是 a[x].second+self(self,x+1,sp-a[x].first), 要使得这个为0 )
        // 或者别这样写了↑ ,这样也不利于写递推公式
        if(dp[x][sp]>-1)return dp[x][sp];
        if(x==n){
            // if(sp>=a[n].first)return a[n].second;// 剩余空间放得下一定要放
            // return 0; // 放不下了肯这个局部解只能是0
            if(sp>=a[n].first){
                dp[n][sp]=a[n].second; return dp[n][sp];
            }else{
                dp[n][sp]=0;
                return dp[n][sp];
            }
        }
        int sum ;
        if(sp>=a[x].first){
            sum = max( self(self,x+1,sp), a[x].second+self(self,x+1,sp-a[x].first) );
        }else{
            sum = self(self,x+1,sp);
        }
        dp[x][sp]=sum;
        return dp[x][sp];
    };
    f(f,1,m);
    cout<<dp[1][m]<<endl;
}

现在从下往还上推回去

void solve(){
    int n,m;cin>>n>>m;

    vector<pair<int,int>> a(n+1);

    rep(i,1,n){
        int v,w;cin>>v>>w;
        a[i].first=v;a[i].second=w;
    }
    vector<vector<int>> dp(n+1,vector<int> (m+1));

    rep(sp,1,m){
        if(sp<a[n].first)dp[n][sp]=0;
        else dp[n][sp]=a[n].second;
    }
    for(int i=n-1;i>=1;i--){
        for(int j=1;j<=m;j++){
            if(j>=a[i].first)dp[i][j]=max( dp[i+1][j], a[i].second+dp[i+1][j-a[i].first] );
            else dp[i][j]=dp[i+1][j];
        }
    }

    cout<<dp[1][m]<<endl;
}

perfect!!!

Acwing 二维费用的背包问题

有 N 件物品和一个容量是 V 的背包,背包能承受的最大重量是 M。

每件物品只能用一次。体积是 vi,重量是 mi,价值是 wi。

求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。
输出最大价值。

输入格式

第一行三个整数,N,V,M,用空格隔开,分别表示物品件数、背包容积和背包可承受的最大重量。

接下来有 N 行,每行三个整数 vi,mi,wi,用空格隔开,分别表示第 i 件物品的体积、重量和价值。

输出格式

输出一个整数,表示最大价值。

数据范围

0<N≤1000
0<V,M≤100
0<vi,mi≤100
0<wi≤1000

输入样例

4 5 6
1 2 3
2 4 4
3 4 5
4 5 6

输出样例:

8
void solve(){
    int n,v,m;cin>>n>>v>>m;
    struct file{
        int v,m,w;
    };
    vector<file> a(n+1);
    rep(i,1,n){
        int v,m,w;cin>>v>>m>>w;
        a[i].v=v;a[i].m=m;a[i].w=w;
    }

    vector<vector<vector<int>>> dp(n+1,vector<vector<int>>(v+1,vector<int>(m+1,-1)));

    auto f=[&](auto self , int x, int spv , int spm)->int{
        if(dp[x][spv][spm]!=-1)return dp[x][spv][spm]; // 不能是if(dp[x][spv][spm])return, 否则超时
        if(x==n){
            if(spv>=a[n].v && spm>=a[n].m){
                return dp[x][spv][spm]=a[n].w;
            }else{
                return dp[x][spv][spm]=0;
            }
        }
        int sum ;
        if(spv>=a[x].v && spm>=a[x].m){
            sum=max( self(self,x+1,spv,spm) , a[x].w + self(self, x+1,spv-a[x].v,spm-a[x].m));
        }else{
            sum=self(self,x+1,spv,spm);
        }
        dp[x][spv][spm]=sum;
        return sum;
    };
    f(f,1,v,m);

    cout<<dp[1][v][m]<<endl;

}

不能是if(dp[x][spv][spm])return; ?

  • dp 数组初始值为 0,而很多有效状态的最大价值本身就可能是 0(例如:选不了任何物品时)。
  • 当某个状态的最大价值为 0 时,dp[x][spv][spm] 会被赋值为 0,但下次访问该状态时,if(dp[...]) 会判定为 false(因为 0 为假),导致该状态被重复计算

那建议以后未访问的都设为-1

Acwing 完全背包问题

有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。

第 i 种物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式

第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 种物品的体积和价值。

输出格式

输出一个整数,表示最大价值。

数据范围

0<N,V≤1000
0<vi,wi≤1000

输入样例

4 5
1 2
2 4
3 4
4 5

输出样例:

10
void solve(){
    int n,m;cin>>n>>m;
    vector<pair<int,int>> a(n+1);

    rep(i,1,n){
        int v,w;cin>>v>>w;
        a[i].first=v;a[i].second=w;
    }
    vector<vector<int>> dp(n+1,vector<int>(m+1,-1));
    auto f = [&](auto self, int x, int sp)->int{
        if(dp[x][sp]>-1){
            return dp[x][sp];
        }
        if(x==n){
            int sum = 0;
            int tem_sp=sp;
            while(tem_sp>=a[n].first){ // 不是sp>0, 要大于最后一个物品的大小
                sum+=a[n].second;
                tem_sp-=a[n].first;
            }
            return  dp[x][sp]=sum;// 
        }
        int sum;
        if(sp>=a[x].first){
            sum = max(a[x].second+self(self,x,sp-a[x].first),self(self,x+1,sp));
        }else{
            sum = self(self,x+1,sp);
        }
        dp[x][sp]=sum;//
        return sum;//
    };
    f(f,1,m);
    cout<<dp[1][m]<<endl;
    
}

直到做到这里才猛然回头发现前面的一些记忆化搜索写的并不规范: 在处理边界的时候, 会漏掉一些对 边界 dp数组 赋值的情况 ( 导致如果层数x 只有1, 那么输出的dp数组就变成-1了) 所以建议return 都return dp [x] [sp] = ...;

写个递推吧

void solve(){
    int n,m;cin>>n>>m;
    vector<pair<int,int>> a(n+1);

    rep(i,1,n){
        int v,w;cin>>v>>w;
        a[i].first=v;a[i].second=w;
    }

    vector<vector<int>> dp(n+1,vector<int>(m+1));// 不用初始化 -1 了
    rep(sp,1,m){ // 1~m
        dp[n][sp]+=(sp/a[n].first)*a[n].second;
    }
    for(int i=n-1;i>=1;i--){
        for(int j=1;j<=m;j++){
            if(j>=a[i].first){
                dp[i][j]=max(a[i].second+dp[i][j-a[i].first],dp[i+1][j]);
            }else{
                dp[i][j]=dp[i+1][j];
            }
        }
    }
    cout<<dp[1][m]<<endl;    
}

P10955 正整数拆分

题目描述

给定一个正整数 N,要求把 N 拆分成若干个正整数相加的形式,参与加法运算的数可以重复。

注意:

  • 拆分方案不考虑顺序;
  • 至少拆分成 2 个数的和。

求拆分的方案数 mod 2147483648 的结果。

emmmm这题就是一个完全背包题, n为空间, 每个数的大小为每个物品空间, 物品价值都为0 但是要算有多少种装法

dp[x] [ sp ] 记录选 到第x 个数, 还余下多少"空间"

如果能选 x : 接下来要计算的是 dp [ x ] [sp- x]

​ 如果不选了 : 计算的是 dp [x+1], [sp]

如果不能选: 计算 dp [x ] [ sp ]

所以, dp[ x] [ sp ] = dp [ x ] [sp- x] + dp [x+1] [sp]

void solve(){
    int n;cin>>n;
    int mod=2147483648;

    vector<vector<int>> dp(n+1,vector<int>(n+1,-1));
    auto f=[&](auto self, int x ,int sp)->int{
        if(x==n)return dp[x][sp]=0;
        if(sp<x)return dp[x][sp]=0;
        if(dp[x][sp]>-1)return dp[x][sp];
        if(sp==x)return dp[x][sp]=1;
        // 现在一定sp>x;
        int sum=0;
        sum  = (self(self,x,sp-x)%mod+self(self,x+1,sp)%mod)%mod;
        return dp[x][sp]=sum;
    };
    f(f,1,n);
    cout<<dp[1][n]<<endl;
}

Leetcode 343. 整数拆分

给定一个正整数n (2<=n<=58), 将其拆分为k ( k>=2 ) 个正整数的和, 并使这些整数的乘积最大化

输出最大化的乘积

和上一题类似

dp[ x ] [ sp ] 表示选到第 x个数且剩余空间为sp时的 最大化的乘积

dp [ x ] [ sp ] = max( chose: x*dp[ x] [ sp ] , not chose: dp [ x ] [ sp ])

void solve(){
    int n;cin>>n;

    vector<vector<int>> dp(n+1,vector<int>(n+1,-1));
    auto f=[&](auto self, int x ,int sp)->int{
        if(x==n)return dp[x][sp]=0;
        if(sp<x)return dp[x][sp]=0;//都是不合法的拆分方案
        if(dp[x][sp]>-1)return dp[x][sp];
        if(sp==x)return dp[x][sp]=x;
        int sum  = max( x*self(self,x,sp-x), self(self,x+1,sp));
        return dp[x][sp]=sum;
    };
    f(f,1,n);
    cout<<dp[1][n]<<endl;
}

B3637 最长上升子序列

题目描述

给出一个由 n(n≤5000) 个不超过 106 的正整数组成的序列。请输出这个序列的最长上升子序列的长度。

最长上升子序列是指,从原序列中按顺序取出一些数字排在一起,这些数字是逐渐增大的。

输入格式

第一行,一个整数 n,表示序列长度。第二行有 n 个整数,表示这个序列。

输出格式

一个整数表示答案。

输入输出样例

输入 #1

6
1 2 4 1 3 4

输出 #1

4
void solve(){
    int n;cin>>n;
    vector<int> a(n+1);
    rep(i,1,n)cin>>a[i];
    
    vector<int> dp(n+1,-1); // 表示以a[x]开头的最长上升子序列长度!!!!!!!!!!!

    auto f=[&](auto self,int x)->int{
        if(dp[x]>-1)return dp[x];
        int len=1;
        for(int i=x+1;i<=n;i++){
            if(a[x]<a[i]){
                len= max ( len, self(self,i)+1 ) ; // 扫一遍更新, 递归更新
            }
        }
        return dp[x]=len;
    };
    int ans=0;
    rep(i,1,n){
        ans=max(ans,f(f,i));// 答案并不一定是a[1]开头的dp[1], 所以要把每个都开头都调用一遍函数, 反正有记忆化
    }
    cout<<ans<<endl;

}

B4133 [信息与未来 2014] 最大连续部分(子段)和

题目描述

n 个整数排成一排,求其中的最大连续部分和。

至少要选一个数。

输入格式

第一行一个整数,表示 n

第二行 n 个整数 x1,x2,⋯,xn,相邻两数之间有一个空格。1≤n≤107,0≤∣xi∣≤108。

输出格式

一个整数(即最大的连续的部分和)。

输入输出样例

输入 #1

7
-2 13 12 9 14 -10 2

输出 #1

48
void solve(){
    int n;cin>>n;
    vector<int> a(n+1);
    rep(i,1,n)cin>>a[i];

    // 每个数都有可能是开头, 
    vector<int> dp(n+1,-1); // 以a[x]开头的最大子段和
    auto f=[&](auto self, int x)->int{
        if(dp[x]>-1)return dp[x];
        if(x==n){
            dp[n]=a[n];return dp[n];
        }
        int sum=a[x];
        sum = max(sum, a[x]+self(self,x+1));
        return dp[x]=sum;
    };
    int ans=-1e9;
    rep(i,1,n){
        ans=max(ans,f(f,i));
    }
    cout<<ans<<endl;
}

但是这样有一个测试点MLE了

写递推?

void solve(){
    int n;cin>>n;
    vector<int> a(n+1);
    rep(i,1,n)cin>>a[i];

    vector<int> dp(n+1); 
    dp[n]=a[n];
    for(int i =n-1;i>=1;i--){
        dp[i]=max(a[i],a[i]+dp[i+1]);
    }
    int ans=-1e9;
    rep(i,1,n)ans=max(ans,dp[i]);
    cout<<ans<<endl;
}

成功AC

posted @ 2025-07-20 14:26  byxxx  阅读(16)  评论(0)    收藏  举报