动态规划基础学习②:线性DP


DP时间复杂度:状态量*每层状态转移的计算量;
当dp要用到上一层的状态如f(i-1)时,下标最好从1开始,下标0直接初始化为0,避免越界。


A:数字三角形

入门题,稍作推导便知道第(i,j)个位置的最大值计算路径必然来自其上的(i-1,j-1)和(i-1,j)两点。

#include <bits/stdc++.h>
using namespace std;

const int N=510,INF=1e9;
int n;
int f[N][N],a[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++) //因为计算涉及到(i-1,j-1)(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 ans=-INF;
    for(int i=1;i<=n;i++)   //遍历最底下一层得到最大值
        ans=max(ans,f[n][i]);

    cout<<ans<<endl;
    return 0;
}

B:最长上升子序列I

模板题 O(n^2)

#include <bits/stdc++.h>
using namespace std;

const int N=1010;
int n,f[N],a[N];

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
        
    for(int i=1;i<=n;i++)
    {
        f[i]=1;
        for(int j=1;j<i;j++)
            if(a[j]<a[i])
                f[i]=max(f[i],f[j]+1);
    }
    
    int ans=0;
    for(int i=1;i<=n;i++)
        ans=max(ans,f[i]);
    cout<<ans<<endl;
    
    return 0;
}

如果要保存最长子序列:

#include <bits/stdc++.h>
using namespace std;

const int N=1010;
int n,f[N],a[N];
int g[N];	//存最长上升子序列

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>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;	//保存f[k]从哪转移的(下标)
                }
    }
    
    int k=1;	//记下标
    for(int i=1;i<=n;i++)
        if(f[k]<f[i])
        	k=i;
    cout<<f[k]<<endl;

	for(int i=0,len=f[k];i<len;i++)	//这是倒序输出(降序)
	{
		cout<<a[k]<<endl;	//因为f[k]表示的是以a[k]结尾的最长上升子序列长度,所以输出a[k]
		k=g[k];	//f[k]是从g[k]转移过来的
	}
    
    return 0;
}

C:最长上升子序列II

这题把数据加到了n<=100000,显然要对上面的算法进行优化。
(显然我们直接记牢这个算法即可hhh)

#include <bits/stdc++.h>
using namespace std;

const int N = 100010;
int n,q[N],a[N];
//q[k]:a序列从第1个元素至第k个元素,所有以a[k]为结尾的上升子序列中,最小的结尾元素值。注意,这里保证了以a[k]结尾的上升子序列的末尾元素一定是最小的!

int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
        cin>>a[i];

    int len = 0;//长度为len的上升子序列
    q[0]=-2e9;//初始化
    for(int i=0;i<n;i++)
    {
        int l=0,r=len;
        while(l<r)//二分查找
        {
            int mid=l+r+1>>1;
            if(q[mid]<a[i]) l=mid;
            else r=mid-1;
        }
        len=max(len,r+1);
        q[r+1]=a[i];
    }
    cout<<len<<endl;
    return 0;
}




D:最长公共子序列

f是dp数组,f(i,j)是集合:所有在a序列前i个字母中出现,且在b序列的前j个字母中出现的公共子序列
那么f(i,j)由四种情况转移:
(1)不包含a[i]、b[i] 👉 f[i-1][j-1]
(2)不包含a[i],包含b[i] 👉 f[i-1][j](左边情况含于右式,在求最大值的问题中可以借助右式进行计算)
(3)包含a[i],不包含b[i] 👉 f[i][j-1](同理)
(4)包含a[i]、b[i] 👉 f[i-1][j-1]+1(去掉第a[i]、b[j],在直接加上1,代表a[i]、b[j]已经必选)

注意:情况(1)显然含于情况(2、3),所以可以省略。

#include <bits/stdc++.h>
using namespace std;

const int N=1010;
int n,m;
char a[N],b[N];
int f[N][N];//这里的f是dp数组,f(i,j)是集合:所有在a序列前i个字母中出现,且在b序列的前j个字母中出现的公共子序列

int main()
{
    cin>>n>>m;
    cin>>a+1>>b+1;
    
    for(int i=1;i<=n;i++)	//两层for进行状态枚举
        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;
}

E:最短编辑距离

这题是公共子序列的应用。

#include <bits/stdc++.h>
using namespace std;

const int N=1010;
char a[N],b[N];
int n,m;
int f[N][N];
//f(i,j)表示a的前i个字母与b的前j个字母相匹配

int main()
{
    cin>>n,cin>>a+1;
    cin>>m,cin>>b+1;
	
	//初始化f数组
    for(int i=0;i<=n;i++)//a的前0~n个长度的串与b的长度为零(就是空的意思)相比,只能删,删i个
        f[i][0]=i;
    for(int i=0;i<=m;i++)//a的长度为0的串(就是空的意思)与b的前0~n个长度的串相比,只能添,添i个
        f[0][i]=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);
            f[i][j]=min(f[i][j],f[i-1][j-1]+(a[i]!=b[j]));
        }
    cout<<f[n][m]<<endl;

    return 0;
}

F:编辑距离

也是对公共子序列的应用,与上题差不多。

#include <bits/stdc++.h>
using namespace std;

const int N=15,M=1010;
int n,m,f[N][N];
char str[N][N];

int edit_distance(cahr a[],char b[])
{
    int la=strlen(a+1),lb=strlen(b+1);//注意这里是从下标1开始

    for(int i=0;i<=la;i++) f[i][0]=i;  //对dp数组进行初始化
    for(int i=0;i<=lb;i++) f[0][i]=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);//注意这里的写法
            f[i][j]=min(f[i][j],f[i-1][j-1]+(a[i]!=b[j]);
        }

    return f[la][lb];
}

int main()
{
    cin>>n>>m;
    for(int i=0;i<n;i++)
        cin>>str[i]+1;

    while(m--)
    {
        char s[N];
        int limit;
        cin>>s+1,cin>>limit;

        int ans=0;
        for(int i=0;i<n;i++)
            if(edit_distance(str[i],s)<=limit)
                ans++;

        cout<<ans<<endl;
    }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值