动态规划经典问题总结

子串和子序列

子串:母串中连续的一段字符串

子序列:所有元素都属于母串,并保留原来的顺序,但是元素在母串中不一定相邻

子串一定是子序列,但子序列不一定是子串


最大连续子串和

求母串中最大的区间和,

用dp[i]表示以i结尾的连续子串的最大和,

若dp[i-1]<=0,dp[i]=a[i]   若dp[i-1]>0,dp[i]=dp[i-1]+a[i];

即,以上一个元素结尾的连续子串的最大和<=0时,舍弃之前的子串,从a[i]重新开始。

以上一个元素结尾的连续子串的最大和>0时,继承上一个元素的最大和

for(int i=1;i<=N;i++)
    {
        scanf("%lld",&x);
        sum=max(sum,(long long)0)+x;
        answer=max(sum,answer);
    }

最长递增子序列(LIS)

O(n^2)做法:

dp[i]表示以Ai为结尾的最长递增子序列的和。

对于每个元素,

①以自己为子序列开头,

②寻找之前子序列中,最后一个元素小于当前元素,并且子序列和最大的那个子序列,充当子序列的末尾

每个dp[i]的初始值为1,即 以自己开头,然后遍历之前dp[i],寻找符合条件且和最大的子序列

for (int i = 1;i <= n;i++)
{ 
    dp[i] = 1;
    int j = i-1;
    while (j > 0)
    { 
        if (a[i] > a[j])
        { 
            dp[i] = max(dp[i],dp[j]+1);
        } 
        j--;
    }
    ans = max(dp[i],ans);
} 

O(n*logn)做法:

用f[j]表示长度为j的最长递增子序列最后一项的最小值

处理每一个a[i],

a[i]可以做满足a[i]>f[j]的长度为j+1的递增子序列的最后一项

如果,a[i]>f[j], f[j+1]=min(f[j+1],a[i]),寻找j,使f[j]<a[i]<=f[j+1],并更新f[j+1];

即找f[]中第一个>=a[i]的j+1的位置(lower_bound),并更新它,这样就维护了f[]数组的单调递增

如果a[i]>f[]数组的最后一位,直接f[cnt++]=a[i];

for(int i=1;i<=N;i++)
{
    scanf("%lld",&a[i]);
    if(i==1)
    {
        dp[cnt++]=a[i];
        continue;
    }
    if(a[i]>dp[cnt-1])
        dp[cnt++]=a[i];
    else
    {
        int temp=lower_bound(dp,dp+cnt,a[i])-dp;
        //lower_bound()用于查找有序区间中第一个大于或等于某给定值的位置
        dp[temp]=a[i];
    }
}

最长公共子串

dp[i][j]表示以A序列第i项和B序列第j项为结尾的最长公共子串的长度

A[i] = B[j]时,dp[i][j] = dp[i-1][j-1] + 1;继承上一位对应的结果

A[i] != B[j]时,dp[i][j] = 0;

for(int i = 0;i <= length_a;i++) 
    dp[i][0] = 0;
for(int i = 0;i <= length_b;i++) 
    dp[0][i] = 0; 
for(int i= 1;i <= length_a;i++)
{ 
    for (int j = 1;j <= length_b;j++)
    {
        if (a[i] == b[j])
            dp[i][j] = dp[i-1][j-1] + 1; 
        else
         	dp[i][j] = 0; 
        ans = max(ans,dp[i][j]);
    }
} 

最长公共子序列

dp[i][j]表示A序列前i项和B序列前j项的最长公共子序列的长度。

(1)a[i] = b[j]时,dp[i][j] = dp[i-1][j-1]+1          (2)a[i] != b[j]时,dp[i][j] = max(dp[i-1][j], dp[i][j-1])

如果a[i]==b[j],则继承上一位的对应的值。如果a[i]!=a[j],则继承dp[i-1][j],dp[i][j-1]中的较大的那个。

dp[length_a][length_b]即为答案。

for(int i = 0;i <= length_a;i++) 
    dp[i][0] = 0;
for(int i = 0;i <= length_b;i++) 
    dp[0][i] = 0; 
for(int i= 1;i <= length_a;i++)
{ 
    for (int j = 1;j <= length_b;j++)
    {
        if (a[i] == b[j])
            dp[i][j] = dp[i-1][j-1] + 1; 
        else
            dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
    }
} 

 如果要输出路径,则回溯求解

int i=length_a;
int j=length_b;
int cnt=0;
while(1)
{
    if(i==0||j==0)//其中一个数组回溯到开头就结束
        break;
    if(a[i]==b[j])
    {
        s[cnt++]=a[i];//答案存放到s[]数组中
        i--;
        j--;
    }
    //回溯到dp[i-1][j]、dp[i][j-1]中较大的那个
    else if(dp[i-1][j]>=dp[i][j-1])
        i--;
    else
        j--;
}
for(int i=cnt-1;i>=0;i--)//倒着输出s[]数组,即为所求最大公共子序列
    printf("%c",s[i]);
printf("\n");

回溯求解型

以HDU1176(免费馅饼)为例,先往dp[][]数组里填初始值。然后从最后一层倒着dp,从最后一层推出倒数第二层的答案

因为此题对时间和初始位置有限制,所以同时满足时间要求和初始位置要求的答案才是正解

for(int i=max_time-1;i>=0;i--)
{
    for(int j=0;j<=10;j++)
    {
        if(j==0)
            dp[i][j]+=max(dp[i+1][j],dp[i+1][j+1]);
        else if(j==10)
            dp[i][j]+=max(dp[i+1][j],dp[i+1][j-1]);
        else
        {
            int temp=0;
            temp=max(dp[i+1][j],dp[i+1][j-1]);
            temp=max(temp,dp[i+1][j+1]);
            dp[i][j]+=temp;
        }
    }
}

int answer;
answer=dp[0][5];

更新中……

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值