【动态规划1】动态规划的引入

前言

动态规划是一种重要的思维方法,通过利用已有的子问题信息高效求出当前问题的最优解。

题单地址:https://www.luogu.com.cn/training/211#problems

P1216 [USACO1.5][IOI1994]数字三角形 Number Triangles(入门题)

思路:动态规划 a + b 题,分别有从上到下DP(需要遍历最大值)和从下到上DP(唯一),不说了,直接上代码。
状态转移:在这里插入图片描述

// 从上到下DP
#include <iostream>

using namespace std;

const int N = 510, INF = 1e9;

int n;
int a[N][N];
int f[N][N];

int main()
{
    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 ++ )
        for(int j = 0;j <= i + 1; j ++ )   // 多初始化边界
            f[i][j] = -INF;
    
    f[1][1] = a[1][1];
    
    for (int i = 2; i <= n;i ++ )
        for (int j = 1; j <= i; j ++ )
            f[i][j] = max(f[i - 1][j - 1] + a[i][j], f[i - 1][j] + a[i][j]);
    
    int res = -INF;
    for (int i = 1; i <= n; i ++ ) res = max(res, f[n][i]);
    
    cout << res << endl;
        
    return 0;
}

// 从下到上DP
#include <iostream>

using namespace std;

const int N = 510, INF = 1e9;

int n;
int a[N][N];
int f[N][N];

int main()
{
    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++)
        for(int j=1;j<=i;j++)
            if(i==n) f[i][j] = a[i][j];   // 初始化
            else f[i][j]=-INF;
            
    for(int i=n-1;i>=1;i--)
        for(int j=1;j<=i;j++)
            f[i][j]=max(f[i+1][j],f[i+1][j+1]) + a[i][j];
            
    cout<<f[1][1]<<endl;
    
    return 0;
    
    
}
P1434 [SHOI2002]滑雪(记忆化搜索)

思路:状态转移条件:w[x][y] > w[a][b]

#include <iostream>

using namespace std;

const int N = 110;

int n,m;
int w[N][N];
int f[N][N];

int dx[4]={-1,0,1,0}, dy[4] = {0,1,0,-1};

int dfs(int x,int y)
{
    if(f[x][y]) return f[x][y];
    f[x][y]=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)
        {
            if(w[x][y] > w[a][b])
            {
                dfs(a,b);
                f[x][y] = max(f[x][y],f[a][b] + 1);
            }
        }
    }
    return f[x][y];
}

int main()
{
    cin >> n >> m;
    
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            cin >> w[i][j];
    
    
    int res=0;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            res=max(res,dfs(i,j));
    
    
    cout<<res<<endl;
    
    return 0;
}
P2196 挖地雷(DP,记忆化搜索)

思路:本题需要额外记录路径,记录路径的常用方法:开一个pre[]前缀数组,记录当前状态是由哪个状态转移过来的,下面给出正推,逆推,记忆化搜索的代码
状态转移条件:g[i][j] == 1 ,表示ij直接存在边

代码1(正推,需要缓存数组)

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

const int N = 25;

int n;
int a[N];
int g[N][N];
int f[N];
int pre[N];

int main()
{
    cin >> n;
    
    for(int i=1;i<=n;i++) cin >> a[i],f[i] = a[i];
    
    for(int i=1;i<=n-1;i++)
        for(int j=i+1;j<=n;j++)
            cin >> g[i][j];
    
    for(int i=1;i<=n;i++)
        for(int j=i+1;j<=n;j++)
        {
            if(g[i][j]) // 当g[i][j] == 1时,状态转移 f[j] = f[i] + a[j];
            {
                if(f[j] < f[i] + a[j])
                {
                    f[j] = f[i] + a[j];
                    pre[j] = i; // 记录前缀
                }
                //f[j] = max(f[j],f[i] + a[j]);
            }
        }
        
    // 以下是输出*************************************************
    int t =0; // 记录最大值的下标
    for(int i=1;i<=n;i++) 
        if(f[t] < f[i])
        {
            t=i;
        }
    int ans = f[t];

    // 正着推需要缓存,倒着推会比较好
    vector<int> res;
    while(pre[t] != 0)
    {
        res.push_back(t);
        t = pre[t];
    }
    res.push_back(t);
    reverse(res.begin(),res.end());
    
    for(int i=0;i<res.size();i++) cout<<res[i]<<" ";
    cout<<endl;
    cout<<ans;
   
    
    return 0;
}

代码2(逆推)

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

const int N = 25;

int n;
int a[N];
int g[N][N];
int f[N];
int pre[N];

int main()
{
    cin >> n;
    
    for(int i=1;i<=n;i++) cin >> a[i],f[i] = a[i];
    
    for(int i=1;i<=n-1;i++)
        for(int j=i+1;j<=n;j++)
            cin >> g[i][j],g[j][i] = g[i][j];// 无向边,倒着搜需要用到
    
    // 倒着搜比较好回溯路径
    for(int i=n;i>=1;i--)
        for(int j=i-1;j>=1;j--)
        {
            if(g[i][j]) // 当g[i][j] == 1时,状态转移 f[j] = f[i] + a[j];
            {
                if(f[j] < f[i] + a[j])
                {
                    f[j] = f[i] + a[j];
                    pre[j] = i; // 记录前缀
                }
                //f[j] = max(f[j],f[i] + a[j]);
            }
        }
        
    // 以下是输出*************************************************
    int t =0; // 记录最大值的下标
    for(int i=1;i<=n;i++) 
        if(f[t] < f[i])
        {
            t=i;
        }
    int ans = f[t];

    // 正着推需要缓存,倒着推会比较好,以下是倒着搜就不用缓存
    vector<int> res;
    while(pre[t] != 0)
    {
        cout<<t<<" ";
        t=pre[t];
    }
    cout<<t<<endl; 
    
    cout<<ans;
   
    
    return 0;
}

代码3(记忆化搜索)

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

const int N = 25;

int n;
int a[N];
int g[N][N];
int f[N];
int ne[N];

int dfs(int u) // 返回这个点能挖到地雷的数量
{
    if(f[u]) return f[u]; // 记忆化搜索
    f[u] = a[u];
    
    int maxv = 0;
    for(int i = u + 1;i <= n; i ++ )
        if(g[u][i]) 
        {
            if(dfs(i) > maxv)
            {
                maxv = dfs(i);
                ne[u] = i;
            }
            //maxv = max(maxv,dfs(i)); // 取最大
        }

    f[u] += maxv;
    
    return f[u];
}

int main()
{
    cin >> n;
    
    for(int i=1;i<=n;i++) cin >> a[i];
    
    for(int i=1;i<=n-1;i++)
        for(int j=i+1;j<=n;j++)
            cin >> g[i][j];
    
    int res=0,t=-1;
    for(int i=1;i<=n;i++)
    {
        if(res < dfs(i))
        {
            res = dfs(i);
            t = i;
        }
    }
    while(ne[t] != t)
    {
        cout<<t<<" ";
        t = ne[t];
    }
    cout<<endl;
    cout<<res<<endl;
   
    return 0;
}
P4017 最大食物链计数(DP,拓扑排序)

思路:状态转移:对于t的所有出边jf[j] = (f[t] + f[j]) % mod
不说了,拓扑排序模板学一手好吧

#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>

using namespace std;

const int N = 500010, mod = 80112002;

int n,m;
int h[N],e[N],ne[N],idx;
int d[N],out[N]; // 记录每个点的入度,出度
int f[N]; // 到这个点的食物链条数

void add(int a,int b)
{
    e[idx] = b,ne[idx] = h[a] ,h[a] = idx ++ ;
}

void topsort()
{
    queue<int> q;
    
    // 寻找入度为0的点,生产者
    for(int i=1;i<=n;i++)
        if(d[i] == 0)
        {
            q.push(i);
            f[i] = 1;
        }
    
    while(q.size())
    {
        int t = q.front();
        q.pop();
        
        for(int i=h[t];i!=-1;i=ne[i]) // 遍历所有出边
        {
            int j = e[i];
            d[j] --;
            f[j] = (f[t] + f[j]) % mod; // 状态转移
            if(d[j] == 0) q.push(j);
        }
    }
}

int main()
{
    
    memset(h,-1,sizeof h); // 记得初始化头结点
    scanf("%d%d",&n,&m);
    
    int a,b;
    while(m--)
    {
        scanf("%d%d",&a,&b);
        add(a,b);
        d[b] ++ ;
        out[a] ++ ;
    }
    
    topsort(); // 忘记调用了~~
    
    int res=0;
    for(int i=1;i<=n;i++)
        if(out[i] == 0) res = (res + f[i]) % mod;  // 出度为0的点为消费者
    
    printf("%d\n",res);
    return 0;
}
P1048 采药(01背包)

思路:01背包裸题,时间看成体积。
这里提供一个优化空间后的写法,注意体积从大到小循环,与下文完全背包区别。

#include <iostream>

using namespace std;

const int N = 100010;

int n,m;
int v[N],w[N];
int f[N];

int main()
{
    cin >> m >> n;
    
    for(int i=1;i<=n;i++) cin >> v[i] >> w[i];
    
    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];
}
P1616 疯狂的采药(完全背包)

思路:完全背包裸题,同样这里提供一种优化空间之后的写法,体积从小到大循环(完全背包比较特别,可特殊记忆,一般来说优化空间都是利用滚动数组优化,体积从大到小。)

#include <iostream>

using namespace std;

const int N = 100010;

int n,m;
int v[N],w[N];
int f[N];

int main()
{
    cin >> m >> n;
    
    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<=m;j++)   // 只有这里与01背包不同
            f[j] = max(f[j],f[j-v[i]] + w[i]);
            
    cout<<f[m];
}
P1802 5倍经验日(01背包变形)

思路:在这里插入图片描述

#include <iostream>
#include <cstdio>

using namespace std;

const int N = 1010;

int n,m;
int lose[N],win[N],use[N];
int f[N];


int main()
{
    cin >> n >> m;
    for(int i=1;i<=n;i++) cin >> lose[i] >> win[i] >> use[i];
    
    for(int i=1;i<=n;i++)
    {
        // j >= use[i],有两种选择
        for(int j=m;j>=use[i];j--) f[j] = max(f[j-use[i]] + win[i],f[j] + lose[i]);  // 如果够药水,有两种选择
        // j < use[i],一种选择
        for(int j = use[i]-1;j>=0;j--) f[j] += lose[i];
    }
    //cout<<f[x] * 5 <<endl;
    printf("%lld",5ll * f[m]); // 爆int
   
    return 0;
}
P1002 过河卒(DP,递推)

思路:状态表示f[i][j] : 从起点(0,0)到(i,j)的路径条数;状态计算:f[i][j] = f[i-1][j] + f[i][j-1]

#include <iostream>
#include <cstdio>

using namespace std;

const int N = 25;

int n,m,x,y;
long long f[N][N];
bool use[N][N]; // 马控制的点

int dx[8]={-1,-2,-2,-1,1,2,2,1}, dy[8]={-2,-1,1,2,2,1,-1,-2};

int main()
{
    cin >> n >> m >> x >> y;
    
    use[x][y] = true; 
    for(int i=0;i<8;i++)
    {
        int a=x + dx[i], b = y + dy[i];
        use[a][b] = true;
    }
    
    f[0][0]=1;
    for(int i=0;i<=n;i++)
        for(int j=0;j<=m;j++)
        {
            if(i) f[i][j] += f[i-1][j];
            if(j) f[i][j] += f[i][j-1];
            if(use[i][j]) f[i][j] = 0;  // 控制点不能走
        }
    
    printf("%lld",f[n][m]); // 爆int
    
    return 0;
}

优化空间之后的写法(滚动数组优化,直接删去第一维)
在这里插入图片描述

#include <iostream>
#include <cstdio>

using namespace std;

const int N = 25;

int n,m,x,y;
long long f[N];
bool use[N][N]; // 马控制的点

int dx[8]={-1,-2,-2,-1,1,2,2,1}, dy[8]={-2,-1,1,2,2,1,-1,-2};

int main()
{
    cin >> n >> m >> x >> y;
    
    use[x][y] = true; 
    for(int i=0;i<8;i++)
    {
        int a=x + dx[i], b = y + dy[i];
        use[a][b] = true;
    }
    
    f[0]=1;
    for(int i=0;i<=n;i++)
        for(int j=0;j<=m;j++)
        {
            if(j) f[j] += f[j-1];
            if(use[i][j]) f[j] = 0;  // 控制点不能走
        }
    
    printf("%lld",f[m]); // 爆int
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值