子串和子序列
子串:母串中连续的一段字符串
子序列:所有元素都属于母串,并保留原来的顺序,但是元素在母串中不一定相邻
子串一定是子序列,但子序列不一定是子串
最大连续子串和
求母串中最大的区间和,
用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];
更新中……