动态规划——线性DP


线性DP入门的三道经典题思路这里就不多说了,直接上代码:

数字三角形:

题目链接:戳

#include
#include
#include
#include
using namespace std;
const int N = 500+10;
int n;
int a[N][N];
int main()
{
    memset(a,-0x1f,sizeof(a));
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=i;j++)
        {
            scanf("%d",&a[i][j]);
            if(i>1)a[i][j]+=max(a[i-1][j],a[i-1][j-1]);
        }
        
    int maxx=-2e+9;
    for(int i=1;i<=n;i++)maxx=max(maxx,a[n][i]);
    printf("%d\n",maxx);
    return 0;
}

LIS问题:

题目链接:戳

#include
#include
#include
#include
using namespace std;
const int N = 1000+10;
int n;
int a[N],f[N];

int main()
{
    scanf("%d",&n);
    int maxx=a[0]=-2e+9;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        for(int j=0;j)
            if(a[j]1);
        maxx=max(maxx,f[i]);
    }
    printf("%d\n",maxx);
    return 0;
}

LCS问题:

题目链接:戳

#include
using namespace std;
char sa[2010],sb[2010];
int f[2010][2010];
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch>'9'|| ch<'0'){if(ch=='-')f=-1;ch=getchar();}
    while(ch<='9'&&ch>='0')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    return x*f;
}
int main()
{
    int la=read(),lb=read();
    scanf("%s%s",sa+1,sb+1);
    for(int i=1;i<=la;i++)
        for(int j=1;j<=lb;j++)
        {
            f[i][j]=max(f[i-1][j],f[i][j-1]);
            if(sa[i]==sb[j])f[i][j]=max(f[i][j],f[i-1][j-1]+1);
         } 
    printf("%d\n",f[la][lb]);
    return 0;
}

LIS问题二分算法 

  题目链接:戳

  这里要讲的是LIS问题二分+贪心算法,可以将时间复杂度从O(n2)降低为O(nlogn)

[算法思路]

  用数组b存储序列a的最长上升子序列,同时记录b的长度len,并用以下方法维护b数组:
枚举a[i],若a[i]>b[len],则直接把a[i]接到b数组后面;否则在b数组中查找第一个大于等于a[i]的数并替换之。由于b数组具有单调性,故可以使用二分查找。

[代码]

#include
#include

using namespace std;

const int N=100000+10;
int a[N],b[N];

int binary_search(int l,int r,int num)
{
    while(l<r)
    {
        int mid=(l+r)>>1;
        if(b[mid]1;
        else r=mid;
    }
    return l;
}

inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    return x*f; 
}

int main()
{
    int n=read(),len=1;
    b[1]=a[1]=read();
    for(int i=2;i<=n;i++)
    {
        if((a[i]=read())>b[len])b[++len]=a[i];
        else b[binary_search(1,len,a[i])]=a[i];
    }
    printf("%d\n",len);
    return 0;
}

编辑距离

  再介绍一道“编辑距离”问题:

  题目链接:戳

  DP思路:对于两个字符串A和B,我们仍然是维护二维的F数组进行DP。F[i,j]表示到字符串A的i位置和字符串B的j位置为止的最小编辑距离。为了得到F[i,j],我们有六种操作,分别是:在Ai-1后面插入字符Bi,将Bj字符删除;将Ai字符删除,在Bi-1后面插入字符Ai;将Ai改成Bj,将Bj改成Ai(仅限于Ai≠Bj时)。显然,这六种操作中,第一和二,三和四,五和六种操作都是等价的,故可以合并为三种。于是我们便可以得到状态转移方程:

  F[i,j]=min{F[i-1][j]+1,F[i][j-1]+1,F[i-1][j-1](if Ai==Bj),F[i-1][j-1]+1(if Ai!=Bj)}

  边界:F[i][0]=F[0][j]=0;目标:F[la][lb]

  代码如下:

#include
#include
#include
#define N 1000+10
using namespace std;
int f[15][15];
char sa[N][15],sb[15];

inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch>'9'|| ch<'0'){if(ch=='-')f=-1;ch=getchar();}
    while(ch<='9'&&ch>='0')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    return x*f;
}

int main()
{
    int n=read(),m=read();
    for(int i=1;i<=n;i++)scanf("%s",sa[i]+1); 
    for(int k=1;k<=m;k++)
    {
        scanf("%s",sb+1);
        int num=read(),sum=0;
        for(int q=1;q<=n;q++)
        {
            int la=strlen(sa[q]+1),lb=strlen(sb+1);
            for(int i=0;i<=la;i++)f[i][0]=i;
            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]+1);
                    if(sa[q][i]==sb[j])
                        f[i][j]=min(f[i][j],f[i-1][j-1]);
                }
            if(f[la][lb]<=num)sum++;
        }
        printf("%d\n",sum);
    }
    return 0;
}