DP动态规划(11.7号前再刷一遍)

背包问题:

Acwing12背包问题求具体方案:

地址:

描述:

思想:

代码:

01背包问题

地址:

描述:

思路:

代码:

完全背包问题

地址:

描述:

思想:

代码: 

 再次优化成一维数组

多重背包问题

地址:

描述:

思想:

代码: 

分组背包问题

地址:

描述:

核心思想

代码

线性DP

Acwing898数字三角形

地址:

描述:

思路:

代码:

ACwing895、896 最长上升子序列

地址:

视频题解:

描述:

思路:

代码:

Acwing897最长公共子序列

地址:

描述:

思路:

代码:

Acwing902最短编辑距离

地址:

描述:

思想:

代码:

Acwing899编辑距离------最短编辑距离的运用

地址:

描述:

代码:

区间DP

Acwing282石子合并

地址:

描述:

思想:

代码:

计数类DP

Acwing900整数划分

地址:

描述:

思想:

代码:

状态压缩DP

Acwing291蒙德里安的梦想(太难之后再看)

闫式dp.jpg

背包问题:

Acwing12背包问题求具体方案:

地址:

12. 背包问题求具体方案 - AcWing题库

描述:

思想:

这里不能将二维转换为一维

注意点一:

首先这题要求我们输出字典序最小的方案:比如123<23

所以这里就要求我们做到,当可以选第1件物品我们就选,实在不行再+1

注意点二:

i从N开始逆序倒推至1和i从1开始正序往后推导至N得到的方案最大值一样的

只不过一个是f[1][V]一个是f[N][V]

注意点三:

为什么i从N~1要逆序?

假如我们让i从1开始正推,那么假如说我们想知道第N个数到底选了没,有以下三种情况:

看f[N][V]是从哪个状态转移来的

1、第N个数没选,则f[N][V]=f[N-1][V];

2、第N个数选了,则f[N][V]=f[N-1][V-v[N]]+w[N]

3、f[N][V]=f[N-1][V]=f[N-1][V-v[N]]+w[N]时表示可选不可选都可以

这是按照字典序最小的原则第N个数我们也要选。

如果按照i从1~N正序的原则,那么我们得从N开始判断这个数选了没选一直判断到1

而如果按照逆序,那么会从1开始判断这个选了没,这个时候才是符合我们字典序逻辑的

代码:

#include<iostream>
using namespace std;
int N,V;
const int M=1010;
int v[M],w[M];
//只从前i个数中取,体积<=j的价格最大数
int f[M][M];
int main(){
    cin>>N>>V;
    for(int i=1;i<=N;i++){
        cin>>v[i]>>w[i];
    }
    //初始化
    for(int i=1;i<=N;i++) f[i][0]=0;
    
    for(int i=N;i>=1;i--)
      for(int j=0;j<=V;j++){
          f[i][j]=f[i+1][j];
          if(j>=v[i])f[i][j]=max(f[i][j],f[i+1][j-v[i]]+w[i]);
      }
        
    
    //f[1][V]是最大值
    int j=V;
    for(int i=1;i<=N;i++)
      {
          if(j>=v[i]&&f[i][j]==f[i+1][j-v[i]]+w[i])
          {
              cout<<i<<" ";
              j-=v[i];
          }
      }
    return 0;     
}

01背包问题

地址:

2. 01背包问题 - AcWing题库

描述:

思路:

二维数组:

简化为一维数组:

为什么可以简化的原因

 

 必须要的原因:

假设这里不进行逆序那么式子应该是
 

 for(int i=1;i<=N;i++){
        for(int j=0;j<=V;j++){
            //为了配合理解这里写上了,是可以省略的
            if(j<v[i]){
                f[j]=f[j];
                //达到效果f[i][j]=f[i-1][j];
            }
            if(j>=v[i]){
                f[j]=max(f[j],f[j-v[i]]+w[i]);
                //由二维数组推导我们想达到的效果是
                //f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
                //但是如果j从0~V正序排,那么我们知道f[j-v[i]]更新一定比f[j]更新要早
                //f[j-v[i]]此时已经变成了f[i][j-v[i]]
                //也就是说当前等式实现的是f[j]=max(f[i][j],f[i][j-v[i]]+w[i]);不对
                //但是如果j从V到0进行逆序那么f[j-v[i]]一定比f[j]更新的晚
                //此时式子就实现了f[j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);成功
            }
        }
    }

代码:

二位数组:

第一种写法便于我们理解(可用于理解,直接看下面优化版)

#include <iostream>
using namespace std;
//N是物品个数,V是容量
int N,V;
const int M=1e3;
//存放某个物品所占的体积和容量
int v[M],w[M];
//f[i][j]前 i 个物品,背包容量 j 下的最优解(最大价值)
int f[M][M];
int main(){
    cin>>N>>V;
    for(int i=1;i<=N;i++) cin>>v[i]>>w[i];
    //显然当i=0,即我们一样物品都不拿时=>f[0][0~m]=0,所以i从1开始
    for(int i=1;i<=N;i++){
        for(int j=0;j<=V;j++){
            //假如说当前背包装不下我们的第i件物品,因此前 i个物品最优解即为前 i−1个物品最优解
            if(j<v[i]){
                f[i][j]=f[i-1][j];
            }
            else{
                f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
            }
        }
    }//for(i)
    cout<<f[N][V]<<endl;
    return 0;
}

 当然这里我们可以再进行一次优化

#include <iostream>
using namespace std;
//N是物品个数,V是容量
int N,V;
const int M=1e3;
//存放某个物品所占的体积和容量
int v[M],w[M];
//f[i][j]前 i 个物品,背包容量 j 下的最优解(最大价值)
int f[M][M];
int main(){
    cin>>N>>V;
    for(int i=1;i<=N;i++) cin>>v[i]>>w[i];
    //显然当i=0,即我们一样物品都不拿时=>f[0][0~m]=0,所以i从1开始
    for(int i=1;i<=N;i++){
        for(int j=0;j<=V;j++){
            //不管三七二十一我们先假设当前背包装不下我们的第i件物品,
            //因此前 i个物品最优解即为前 i−1个物品最优解
                f[i][j]=f[i-1][j];
            //假设实际能装下第i件物品,我们将不能装下和能装下两者进行比较取最大值
                f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
        }
    }//for(i)
    cout<<f[N][V]<<endl;
    return 0;
}

 一维数组:

#include <iostream>
using namespace std;
//N是物品个数,V是容量
int N,V;
const int M=1e3+10;
//存放某个物品所占的体积和容量
int v[M],w[M];
int f[M];
int main(){
    cin>>N>>V;
    for(int i=1;i<=N;i++) cin>>v[i]>>w[i];
    for(int i=1;i<=N;i++){
        for(int j=V;j>=v[i];j--){
           f[j]=max(f[j],f[j-v[i]]+w[i]);
        }
    }
    cout<<f[V]<<endl;
    return 0;
}

完全背包问题

地址:

3. 完全背包问题 - AcWing题库

描述:

思想:

image.png

 优化思路:

代码: 

优化前:

#include <iostream>
using namespace std;
int N,V;
const int M=1e3+10;
int f[M][M];
int v[M],w[M];
int main(){
    cin>>N>>V;
    for(int i=1;i<=N;i++)cin>>v[i]>>w[i];
    for(int i=1;i<=N;i++){
        for(int j=0;j<=V;j++){
            for(int k=0;k*v[i]<=j;k++){
                f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+w[i]*k);
            }
        }
    }//for(i)
    cout<<f[N][V];
}

 优化后:

#include <iostream>
using namespace std;
int N,V;
const int M=1e3+10;
int f[M][M];
int v[M],w[M];
int main(){
    cin>>N>>V;
    for(int i=1;i<=N;i++)cin>>v[i]>>w[i];
    for(int i=1;i<=N;i++){
        for(int j=0;j<=V;j++){
            f[i][j]=f[i-1][j];
            if(v[i]<=j) f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);
        }
    }//for(i)
    cout<<f[N][V];
}

 再次优化成一维数组
这里不需要进行逆序操作

#include <iostream>
using namespace std;
int N,V;
const int M=1e3+10;
int f[M];
int v[M],w[M];
int main(){
    cin>>N>>V;
    for(int i=1;i<=N;i++)cin>>v[i]>>w[i];
    for(int i=1;i<=N;i++){
        for(int j=v[i];j<=V;j++){
        f[j]=max(f[j],f[j-v[i]]+w[i]);
        }
    }//for(i)
    cout<<f[V];
}

多重背包问题

地址:

5. 多重背包问题 II - AcWing题库

描述:

思想:

 优化思路:

2020-03-05_175526.png

2020-03-05_175620.png

 时间复杂度o(Nlogs1v)

代码: 

暴力

#include <iostream>
using namespace std;
int N,V;
const int M=110;
int v[M],w[M],s[M];
int f[M][M];
int main(){
    cin>>N>>V;
    for(int i=1;i<=N;i++) cin>>v[i]>>w[i]>>s[i];
    for(int i=1;i<=N;i++){
        for(int j=0;j<=V;j++){
            for(int k=0;k<=s[i]&&k*v[i]<=j;k++){
               f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+w[i]*k);
            }
        }
    }
    cout<<f[N][V];
    return 0;
}

 优化=>如何转为01背包问题?

#include<iostream>
using namespace std;

const int N = 12010, M = 2010;

int n, m;
int v[N], w[N]; //逐一枚举最大是N*logS
int f[M]; // 体积<M

int main()
{
    cin >> n >> m;
    int cnt = 0; //分组的组别
    for(int i = 1;i <= n;i ++)
    {
        int a,b,s;
        cin >> a >> b >> s;
        int k = 1; // 组别里面的个数
        while(k<=s)
        {
            cnt ++ ; //组别先增加
            v[cnt] = a * k ; //整体体积
            w[cnt] = b * k; // 整体价值
            s -= k; // s要减小
            k *= 2; // 组别里的个数增加
        }
        //剩余的一组
        if(s>0)
        {
            cnt ++ ;
            v[cnt] = a*s; 
            w[cnt] = b*s;
        }
    }

    n = cnt ; //枚举次数正式由个数变成组别数

    //01背包一维优化
    for(int i = 1;i <= n ;i ++)
        for(int j = m ;j >= v[i];j --)
            f[j] = max(f[j],f[j-v[i]] + w[i]);

    cout << f[m] << endl;
    return 0;
}

分组背包问题

地址:

9. 分组背包问题 - AcWing题库

描述:

核心思想

枚举第i组里面我们要选第几个

代码

#include <iostream>
using namespace std;
//物品组数和背包容量
int n,m;
const int N=110;
//表示第i个物品组的物品数量
int s[N];
//分别表示第 i 个物品组的第 j 个物品的体积和价值
int v[N][N],w[N][N];
int f[N];
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>s[i];//输入第i组物品组的个数
        for(int j=1;j<=s[i];j++){
            cin>>v[i][j]>>w[i][j];
        }//for(j)
    }//for(i)
    for (int i = 1; i <= n; i ++ )  //选择第i组物品
        for (int j = m; j >= 0; j -- ) //重量j 
            for (int k = 1; k <= s[i]; k ++ )  //第i组物品的第k个物品
                if (v[i][k] <= j)
                    f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);

    cout << f[m] << endl;

    return 0;
}

线性DP

Acwing898数字三角形

地址:

898. 数字三角形 - AcWing题库

描述:

思路:

从顶往下:(需要特判)

 从底往上:

 

代码:

这里只写逆序版因为正序版边界问题太复杂了

#include<iostream>
using namespace std;
const int N=510;
int a[N][N];
int f[N][N];
//逆序版本
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=i;j++) 
            cin>>a[i][j];
    //三角形最底端的路径和就是这个点本身
    for(int i=1;i<=n;i++) f[n][i]=a[n][i];
    //从倒数第二层开始往上走
   for(int i=n-1;i>=1;i--)
     for(int j=1;j<=i;j++)
       f[i][j]=max(f[i+1][j]+a[i][j],f[i+1][j+1]+a[i][j]);

    cout<<f[1][1];

}

ACwing895、896 最长上升子序列

地址:

895. 最长上升子序列 - AcWing题库

896. 最长上升子序列 II - AcWing题库

视频题解:

找不到页面 - AcWing

描述:

 注意数据范围的区别:

思路:

核心思想枚举倒数第二个数是哪个数,根据这个得到转移方程

注意倒数第二个数一定要严格小于最后一个数

本题最长上升子序列就是1 2 5 6

核心思路依次寻找i从1~n的最长上升子序列再从中找出最长的一条(f[i]表示长度)

i=1 =>3  f[i]=1

i=2 =>3或1   f[i]=1

i=3 =>1 2      f[i]=2

i=4 =>1 2     f[i]=2

i=5 =>1 2 8   f[i]=3

i=6 =>1 2 8或者1 2 5    f[i]=3

i=7 =>1 2 5 6     f[i]=4

最终推出res=4

 转移方程怎么得到?(以i=7为例)

每个集合中都包含最后一个数a[7]=6,所以我们把这个数给删了。

然后枚举倒数第二个数 :它的坐标 j从0~i -1去算一个f[ j ]的最大值,加上最后一个数(子序列的长度+1),就是此时的f[ i ]。

优化思路

代码:

#include <iostream>
using namespace std;
int n;
const int N=1e3+10;
int a[N];//数组
int f[N];//以i为结尾的最长子序列的长度
int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    for(int i=1;i<=n;i++){
        f[i]=1;//只有a[i]一个数最短长度为1
        for(int j=0;j<i;j++){
            //要求倒数第二个数必须小于最后一个数
            if(a[j]<a[i]){
                f[i]=max(f[j]+1,f[i]);
            }
        }
    }
    int res=0;
    //找出以1~n为结尾的子序列中长度的最大值
    for(int i=1;i<=n;i++) res=max(res,f[i]);
    cout<<res;
    return 0;
}

优化:

#include <iostream>
using namespace std;
int n;
const int N=1e5+10;
int a[N];//数组
int q[N];//保存长度为L的最长子序列中结尾最小的数
int main(){
    cin>>n;
    for(int i=0;i<n;i++){
        cin>>a[i];
    }
   int len=0;
   for(int i=0;i<n;i++){
       int l=0,r=len;
       while(l<r){// 二分找到最大的小于当前数a[i]的数的下标
           int mid=l+r+1>>1;
           if(q[mid]<a[i])l=mid;
           else r=mid-1;
       }
       //这里找到了左边最大的小于a[i]的数f[r], 此时的序列长度是原来的长度r 加上 1
       len=max(len,r+1);
       //长度是r + 1的严格上升子序列是以a[i]结尾的
       q[r+1]=a[i];
   }
    cout<<len;
    return 0;
}

Acwing897最长公共子序列

地址:

897. 最长公共子序列 - AcWing题库

描述:

思路:

 

代码:

#include <iostream>
using namespace std;
int n,m;
const int N=1010;
char a[N],b[N];
int f[N][N];
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=m;i++) cin>>b[i];
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            //由于00包含在01和10中所以f[i-1][j-1]可以去除
            f[i][j]=max(f[i-1][j],f[i][j-1]);
            if(a[i]==b[j]) f[i][j]=max (f[i][j],f[i-1][j-1]+1);
        }
    }
    cout<<f[n][m];
}

Acwing902最短编辑距离

地址:

活动 - AcWing

描述:

思想:

状态表示 dp[i][j]
集合 : 所有把a中的前i个字母 变成 b中前j个字母的集合的操作集合
属性 : 所有操作中操作次数最少的方案的操作数


状态计算
状态划分 以对a中的第i个字母操作不同划分
1、在该字母之后添加
     添加之后a[i]与b[j]完全匹配,所以插入的就是b[j] ,那填之前a[1~i]和b[1~(j-1)]匹配
        即 : dp[i][j] = dp[i][j-1] + 1
2、删除该字母
     删除该字母之后变得相同,说明没有删除前a中前i-1已经和b的前j个已经相同
         即 : dp[i][j] = dp[i-1][j] + 1
3、替换该字母
   把a[i]改成b[j]之后想要a[1~i]与b[1~j]匹配 
         那么修改这一位之前,a[1~(i-1)]应该与b[1~(j-1)]匹配=> f[i-1][j-1] + 1
         但是如果本来a[i]与b[j]这一位上就相等,那么不用改=>f[i-1][j-1] + 0


初始化:
先考虑有哪些初始化嘛
1.你看看在for遍历的时候需要用到的但是你事先没有的
(往往就是什么0啊1啊之类的)就要预处理 
2.如果要找min的话别忘了INF
  要找有负数的max的话别忘了-INF 


1.f[0][i]如果a初始长度就是0,那么只能用插入操作让它变成b
  f[i][0]同样地,如果b的长度是0,那么a只能用删除操作让它变成b
2.f[i][j] = INF //虽说这里没有用到,但是把考虑到的边界都写上还是保险

代码:

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int n,m;
const int N=1010;
char a[N],b[N];
int f[N][N];
int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    cin>>m;
    for(int i=1;i<=m;i++){
        cin>>b[i];
    }
    memset(f,0x3f,sizeof f);
    //初始化
    for(int i=0;i<=m;i++) f[0][i]=i;//全添加
    for(int i=0;i<=n;i++) f[i][0]=i;//全删除
    
    for(int i=1;i<=n;i++)
      for(int j=1;j<=m;j++){
          //在增删改三种选项中选出最小的
          //删除和添加可以直接做
          f[i][j]=min(f[i-1][j]+1,f[i][j-1]+1);
          //替换操作
          //替换需要进行一次判断
          if(a[i]==b[j]) f[i][j]=min(f[i][j],f[i-1][j-1]);
          else f[i][j]=min(f[i][j],f[i-1][j-1]+1);
      }
    cout<<f[n][m];
    return 0;
}

Acwing899编辑距离------最短编辑距离的运用

地址:

活动 - AcWing

描述:

代码:

#include <iostream>
#include <string.h>
#include <cstring>
using namespace std;
const int N=15,M=1010;
int n,m;
int f[N][N];
//保存n个字符串
char str[M][N];
int get(char a[],char b[]){
    int la=strlen(a+1),lb=strlen(b+1);
    for(int i=0;i<=lb;i++) f[0][i]=i;
    for(int i=0;i<=la;i++ )f[i][0]=i;
    for(int i=1;i<=la;i++){
        for(int j=1;j<=lb;j++){
            f[i][j]=min(f[i-1][j]+1,f[i][j-1]+1);
            if(a[i]==b[j]){
                f[i][j]=min(f[i-1][j-1],f[i][j]);
            }
            else  f[i][j]=min(f[i-1][j-1]+1,f[i][j]);
        }
    }
    return f[la][lb];
}
int main(){
   cin>>n>>m;
   //为了避免边界问题 每个字符串下标都要从1开始
   for (int i = 0; i < n; i++) {
       //str[0]表示第一个字符串的第一个字符的地址
        cin >> (str[i] + 1);//不加()也可以
    }
    //m次询问
    while(m--){
        //每次询问给出一个字符串和一个操作次数上限
        int res=0;//给定的 n 个字符串中可以在上限操作次数内经过操作变成询问给出的字符串的数目
        char s[N];
        int limit;
        cin>>(s+1)>>limit;
        for(int i=0;i<n;i++){
            if(get(str[i],s)<=limit) res++;
        }
        cout<<res<<endl;
    }
    return 0;
}

区间DP

Acwing282石子合并

地址:

282. 石子合并 - AcWing题库

描述:

思想:

捕获2.PNG

 

 

代码:

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int n;
const int N=310;
int s[N];
int f[N][N];
int main(){
    cin>>n;
    memset(f,0x3f,sizeof f);//初始化
    for(int i=1;i<=n;i++) f[i][i]=0;
    for(int i=1;i<=n;i++) cin>>s[i];
    for(int i=1;i<=n;i++) s[i]+=s[i-1];//前缀和
    
    for(int len=2;len<=n;len++)//区间长度
      for(int i=1;i+len-1<=n;i++)//枚举起点
        {
            int l=i;
            int j=l+len-1;//自动获得终点
            //枚举分割点,构造状态转移方程
            for(int k=l;k<j;k++){
                f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[l-1]);
            }
        }
    cout<<f[1][n];
    return 0;
}

计数类DP

Acwing900整数划分

地址:

活动 - AcWing

描述:

5的7种划分方式为:

11111、2111、221、311、32、41、5

思想:

完全背包法:(这里的属性是数量

把1,2,3, … n分别看做n个物体的体积,这n个物体均无使用次数限制,问恰好能装满总体积为n的背包的总方案数(完全背包问题变形)

整数划分-优化前.jpg

完全背包解法 - 优化

f[i][j] 表示前i个整数(1,2…,i)恰好拼成j的方案数

求方案数:把集合选0个i,1个i,2个i,…全部加起来
f[i][j] = f[i-1][j] + f[i-1][j-i] + f[i-1][j-2i] + ... + f[i-1][j-si]
f[i][j-i] =             f[i-1][j-i] + f[i-1][j-2i] + ... + f[i-1][j-si]
等效替换:f[i][j] = f[i-1][j] + f[i][j-1](类似于完全背包)
优化一维:f[j] = f[j] + f[j-i]

代码:

朴素做法:

//暴力解法
#include <iostream>
using namespace std;
int n;
const int N=1010,mod=1e9+7;
//只从前i个数中取合正好为j的方法数
int f[N][N];
int main(){
    cin>>n;
    //初始化
    for(int i=0;i<=n;i++) f[i][0]=1;
    
    for(int i=1;i<=n;i++)
      for(int j=0;j<=n;j++){
          f[i][j]=(f[i-1][j])%mod;
          if(j>=i) f[i][j]=(f[i-1][j]+f[i][j-i])%mod;
      }
      cout<<f[n][n];
        
}

 简化为一维:

注意j从i开始

//一维简化
#include <iostream>
using namespace std;
int n;
const int N=1010,mod=1e9+7;
//只从前i个数中取合正好为j的方法数
int f[N];
int main(){
    cin>>n;
    //初始化
    for(int i=0;i<=n;i++) f[0]=1;
    
    for(int i=1;i<=n;i++)
    //注意这里的区别j从i开始
      for(int j=i;j<=n;j++){
         f[j]=(f[j]+f[j-i])%mod;
      }
      cout<<f[n];
        
}

状态压缩DP

Acwing291蒙德里安的梦想(太难之后再看)

树形DP问题

没有上司的舞会

描述:

地址:

285. 没有上司的舞会 - AcWing题库

思想:

本题简单地来说就是尽量不要安排子节点(员工)和直接父节点(顶头上司)在一起。

从题意可以得出:

如果选了当前节点u,那么他所有的儿子都不能选
如果不选当前节点u,那么他所有的儿子既可以选,也可以不选

image-20210305120524149

 

代码:

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=6010;
int n;
int happy[N];//每个员工的快乐指数
int e[N],h[N],ne[N],idx;//树形是特殊的图,所以可以用邻接表存储
int f[N][2];//快乐指数总和
//本题没有说明根节点是谁,需要我们自己找
bool has_father[N];//判断一个节点有没有父节点,用于寻找root节点
void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u){、
    f[u][0]=0;//不选
    //选择了u节点就先把该节点的快乐指数加上
    f[u][1]=happy[u];
    for(int i=h[u];i!=-1;i=ne[i]){
        int j=e[i];
        dfs(j);
       f[u][0]+=max(f[j][0],f[j][1]);// 不选择根节点的最大值的状态转移!
       f[u][1]+=f[j][0];//选择根节点的状态转移!
    }
}
int main(){
    cin>>n;
    for(int i=1;i<=n;i++) cin>>happy[i];
    memset (h,-1,sizeof h);
    for(int i=0;i<n-1;i++){
        int a,b;
        cin>>a>>b;
        //注意这里b节点是父节点,a节点是子节点,所以add()顺序要颠倒
        add(b,a);
        //说明a节点是有父节点的
        has_father[a]=true;
    }
    int root=1;
    //寻找root节点,根节点就是没有父节点的节点
    while(has_father[root]) root++;
    dfs(root);
    cout<<max(f[root][0],f[root][1]);
    return 0;
}

记忆化搜索

Acwing901滑雪

地址

901. 滑雪 - AcWing题库

描述

思想

 

代码

#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N=310;
int res=0;
int n,m;
//注意一定是一个拓扑图,即不能有环,本题由于只能划到高度比自己低的点,所以一定不会出现环
int h[N][N];//每一个点的高度
int f[N][N];//从[i,j]点出发可完成的最长滑雪长度
//定义偏移量
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
//返回确定的从[x,y]出发可完成的最长滑雪长度
int dp(int x,int y){
    int &v=f[x][y];//引用字符&,v代表f[x][y];
    if(v!=-1) return f[x][y];//v!=-1表示这个点确定好了
    v=1;//初始化,选了这个点,至少能在这个点滑,所以长度至少为1
    for(int i=0;i<4;i++){
        int a=x+dx[i],b=y+dy[i];
        if(a>=1&&a<=n&&b>=1&&b<=m&&h[a][b]<h[x][y]){
            v=max(v,dp(a,b)+1);
        }
    }
    return v;
}
int main(){
    cin>>n>>m;
    memset(f,-1,sizeof f);//初始化f为-1,表示这个点还没确定
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>h[i][j];
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            res=max(res,dp(i,j));
        }
    }
    cout<<res;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值