动态规划(2)------- 线性DP

上篇讲了背包问题算小小的入个门,这篇为大家浅谈一下线性DP的一些板子题,帮助大家理解一下动态规划。

点这里点这里点这里(数字三角形)

要找到整个数字三角形的最大值,我们可以描述为去找从三角形第一行第一列(a[1][1])到三角形最后一行第?列(a[n][j]),这里用问号表示我们不知道最大值具体在哪里。

我们以从第一行第一列走到第i行第j列来为例,能够走到 a[i][j] 的位置只有上面 a[i-1][j] 和左上角a[i-1][j-1] 两类,也是就是说每个位置只有对应位置的左上角和上一格来决定走到本格的最大值,同理,每个格子都满足(a[1][1] 不满足,因为它是最顶上的格子)。

我们这里设 f[i][j] 表示走到 a[i][j] 的最大值,所以根据分类原理有: f[i][j] = max(f[i-1][j-1], f[i-1][j]) + a[i][j],其中 f[1][1] = a[1][1] // 第一个格的最大值只能是第一个数值

//自向而下
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=520;
const int INF=1e9;
int a[N][N],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++)
        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],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;
}
//自下而上,这个比较舒服,因为肯定超不出边界,即使最后一行超出去也都是0,对结果没影响
#include<bits/stdc++.h>
using namespace std;

const int N=510;
int f[N][N];
int n;

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=i;j++)
        {
            cin>>f[i][j];
        }
    }

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


点这里点这里点这里(最长上升子序列)

下面为大家介绍一种朴实的O(n2)的动态规划,因为复杂度比较高,所以只能做一些范围较小的题。
状态表示:f[i]表示从第一个数字开始算,以a[i]结尾的最大的上升序列。(以a[i]结尾的所有上升序列中属性为最大值的那一个)

状态计算(集合划分):j∈(0,1,2,…,i-1), 在a[i] > a[j]时,
f[i] = max(f[i], f[j] + 1)。
有一个边界,若前面没有比i小的,f[i]为1(自己为结尾)。
最后在找f[i]的最大值。

时间复杂度
O(n2)状态数(nn) * 转移数(nn)

#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=1010;
int a[N],f[N],g[N];
int main()
{
    int n;  cin>>n;
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)
    {
        f[i]=1;
        for(int j=1;j<i;j++)
        {
            if(a[i]>a[j])
                f[i]=max(f[i],f[j]+1);
        }
    }
    int res=0;
    for(int i=1;i<=n;i++)
        res=max(res,f[i]);
    cout<<res<<endl;
    return 0;
}

优化版:
思路:首先数组a中存输入的数(原本的数),开辟一个数组f用来存结果,最终数组f的长度就是最终的答案;假如数组f现在存了数,当到了数组a的第i个位置时,首先判断a[i] > f[cnt] ? 若是大于则直接将这个数添加到数组f中,即f[++cnt] = a[i];这个操作时显然的。
当a[i] <= f[cnt] 的时,我们就用a[i]去替代数组f中的第一个大于等于a[i]的数,因为在整个过程中我们维护的数组f 是一个递增的数组,所以我们可以用二分查找在 logn 的时间复杂的的情况下直接找到对应的位置,然后替换,即f[l] = a[i]。

我们用a[i]去替代f[i]的含义是:以a[i]为最后一个数的严格单调递增序列,这个序列中数的个数为l个。这里为什么说f数组是单调递增的,因为假如3位于2的前面的话,那么能如果比3大,那么一定比2大,所以这时候就冲突了,所以f数组一定是单调递增的。

这样当我们遍历完整个数组a后就可以得到最终的结果。

时间复杂度分析:O(nlogn)

这是优化版本的动态规划(O(nlgn)),有没有感觉这个思想很熟悉(小编个人觉得这个想法和单调栈的想法好相似…)

#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e5+10;
int a[N],f[N],cnt;
int find(int x)//替换掉第一个大于或者等于这个数字的那个数
    int l=1;    int r=cnt;
    while(l<r)
    {
        int mid=l+r>>1;
        if(f[mid]>=x)
            r=mid;
        else
            l=mid+1;
    }
    return l;
}

int main()
{
    int n;  scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);

    f[++cnt]=a[1];
    for(int i=2;i<=n;i++)
    {
        if(a[i]>f[cnt])
            f[++cnt]=a[i];
        else
            f[find(a[i])]=a[i];
    }
    cout<<cnt<<endl;
    return 0;
}


那么怎么将这个序列输出来呢。

#include<iostream>
#include<algorithm>
#include<stdio.h>
using namespace std;
typedef long long ll;
const int N=1010;
int a[N],f[N],g[N];
int main()
{
    int n;  cin>>n;
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)
    {
        f[i]=1;
        g[i]=0;
        for(int j=1;j<i;j++)
        {
            if(a[j]<a[i])
            {
                if(f[i]<f[j]+1)
                {
                    f[i]=f[j]+1;
                    g[i]=j;
                }
            }
        }
    }
    int k=1;
    for(int i=1;i<=n;i++)
        if(f[k]<f[i])
            k=i;
    cout<<f[k]<<endl;
    int len=f[k];
    for(int i=0;i<len;i++)
    {
        cout<<a[k]<<" ";
        k=g[k];
    }
    return 0;
}

点这里点这里点这里(最长公共子序列)


直接看y总的思路。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1010;
int f[N][N];
char a[N],b[N];
int n,m;
int main()
{
    cin>>n>>m>>a+1>>b+1;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            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]<<endl;
    return 0;
}

发布了41 篇原创文章 · 获赞 5 · 访问量 2256

猜你喜欢

转载自blog.csdn.net/mumuhaoshuai/article/details/104095392
今日推荐