数字三角形
很古老很经典的题
dp状态表示
集合: 从下往上走到i, j的路线的集合
属性: maximnum
状态计算
i+1, j i+1, j+1
给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
输入格式
第一行包含整数 n,表示数字三角形的层数。
接下来 n 行,每行包含若干整数,其中第 i 行表示数字三角形第 i 层包含的整数。
输出格式
输出一个整数,表示最大的路径数字和。
数据范围
1≤n≤500,
−10000≤三角形中的整数≤10000
输入样例:
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
输出样例:
30
代码:倒序
#include<bits/stdc++.h>
using namespace std;
const int N = 510;
int n;
int w[N][N];
int f[N][N];
int main()
{
cin>>n;
for( int i = 1; i <= n; i++ )
for( int j = 1; j <= i; j++ )
cin>>w[i][j];
for( int i = 1; i <= n; i++ )
f[n][i] = w[n][i];
for( int i = n-1; i; i-- )
for( int j = 1; j <= i; j++ )
f[i][j] = max(f[i+1][j]+w[i][j], f[i+1][j+1]+w[i][j]);
cout<<f[1][1]<<endl;
return 0;
}
正序:
#include<bits/stdc++.h>
using namespace std;
const int N = 510, INF = 0x3f3f3f3f;
int n;
int w[N][N];
int f[N][N];
int main()
{
cin>>n;
for( int i = 1; i <= n; i++ )
for( int j = 1; j <= i; j++ )
cin>>w[i][j];
memset(f, -0x3f, sizeof f);//设置边界
f[1][1] = w[1][1];
for( int i = 2; i <= n; i++ )
for( int j = 1; j <= i; j++ )
f[i][j] = w[i][j]+max(f[i-1][j-1], f[i-1][j]);
int res = -INF;
for( int i = 1; i <= n; i++ )
res = max(res, f[n][i]);
cout<<res<<endl;
return 0;
}
最长上升子序列
状态表示::以i结尾的上升子序列 求最大值
状态计算 f[i] = max(f[i], f[j]+1)
给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。
输入格式
第一行包含整数 N。
第二行包含 N 个整数,表示完整序列。
输出格式
输出一个整数,表示最大长度。
数据范围
1≤N≤1000,
−109≤数列中的数≤109
输入样例:
7
3 1 2 1 8 5 6
输出样例:
4
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int n;
int a[N];
int f[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 res = 0;
for( int i = 1; i <= n; i++ )
res = max(res, f[i]);
cout<<res<<endl;
return 0;
}
最长上升子序列2(二分优化)
更像贪心,而非DP
f[i]表示长度为i的上升子序列末位最小的数字如果接在r的后面,需要更新一下r+1
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int n;
int a[N], f[N];
int main()
{
cin>>n;
for( int i = 0; i < n; i++ )
cin>>a[i];
f[0] = -2e9;
int len = 0;
for( int i = 0; i < n; i++ ){
int l = 0, r = len;
while(l < r){
int mid = l+r+1>>1;
if(f[mid] < a[i])
l = mid;
else
r = mid-1;
}
len = max(len, r+1);
f[r+1] = a[i];
}
cout<<len<<endl;
return 0;
}
最长上升子序列3(模拟栈)
//查找到的位置减去数组初始位置就是该元素在数组中的位置
int pos = lower_bound(a+x, a+y, z) - a;
a[pos] = t;
//直接对该位置进行赋值操作
*lower_bound(a+x, a+y, z) = t;
这个栈不用于记录最终的最长上升子序列,只是表示以v[i]结尾的子序列最长为i,
这里面同样蕴含贪心的思想,对于同样长度的子串,必然里面的元素越小,以后越有机会拓展
比如:1 3 4 2 7 5 6 8
1- 3 -4 -》 1 - 2 - 4 - 》1- 2 - 4 - 7-》1-2-4- 5-》1-2-4-5-6 -》 1- 2- 4 -5 -6 -8
实际上的最大上升子序列是1 3 4 5 6 8
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int n;
int a[N];
int main()
{
cin>>n;
for( int i = 0; i < n; i++ )
cin>>a[i];
vector<int> v;
v.push_back(a[0]);
for( int i = 1; i < n; i++ )
if(a[i] > v.back())
v.push_back(a[i]);
else
*lower_bound(v.begin(), v.end(), a[i]) = a[i];
cout<<v.size()<<endl;
return 0;
}
最长公共子序列
f[i][j]:所有a【1i】,b【1j】的公共子序列的集合
按两个序列的末位是否相等来划分集合
给定两个长度分别为 N 和 M 的字符串 A 和 B,求既是 A 的子序列又是 B 的子序列的字符串长度最长是多少。
输入格式
第一行包含两个整数 N 和 M。
第二行包含一个长度为 N 的字符串,表示字符串 A。
第三行包含一个长度为 M 的字符串,表示字符串 B。
字符串均由小写字母构成。
输出格式
输出一个整数,表示最大长度。
数据范围
1≤N,M≤1000
输入样例:
4 5
acbd
abedc
输出样例:
3
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int n, m;
char a[N], b[N];
int f[N][N];
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;
}
最短编辑距离
f[i][j]:所有将a[1i]变成b[1j]的操作方式
给定两个字符串 A 和 B,现在要将 A 经过若干操作变为 B,可进行的操作有:
删除–将字符串 A 中的某个字符删除。
插入–在字符串 A 的某个位置插入某个字符。
替换–将字符串 A 中的某个字符替换为另一个字符。
现在请你求出,将 A 变为 B 至少需要进行多少次操作。
输入格式
第一行包含整数 n,表示字符串 A 的长度。
第二行包含一个长度为 n 的字符串 A。
第三行包含整数 m,表示字符串 B 的长度。
第四行包含一个长度为 m 的字符串 B。
字符串中均只包含大写字母。
输出格式
输出一个整数,表示最少操作次数。
数据范围
1≤n,m≤1000
输入样例:
10
AGTCTGACGC
11
AGTAAGTAGGC
输出样例:
4
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int n, m;
char a[N], b[N];
int f[N][N];
int main()
{
cin>>n>>a+1>>m>>b+1;
for( int i = 0; i <= m; i++ )
f[0][i] = i;
for( int i = 0; i <= n; i++ )
f[i][0] = 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);
if(a[i]==b[j])
f[i][j] = f[i-1][j-1];
else
f[i][j] = min(f[i][j], f[i-1][j-1]+1);
}
cout<<f[n][m]<<endl;
return 0;
}
编辑距离
最短编辑距离强化
给定 n 个长度不超过 10 的字符串以及 m 次询问,每次询问给出一个字符串和一个操作次数上限。
对于每次询问,请你求出给定的 n 个字符串中有多少个字符串可以在上限操作次数内经过操作变成询问给出的字符串。
每个对字符串进行的单个字符的插入、删除或替换算作一次操作。
输入格式
第一行包含两个整数 n 和 m。
接下来 n 行,每行包含一个字符串,表示给定的字符串。
再接下来 m 行,每行包含一个字符串和一个整数,表示一次询问。
字符串中只包含小写字母,且长度均不超过 10。
输出格式
输出共 m 行,每行输出一个整数作为结果,表示一次询问中满足条件的字符串个数。
数据范围
1≤n,m≤1000,
输入样例:
3 2
abc
acd
bcd
ab 1
acbd 2
输出样例:
1
3
#include<bits/stdc++.h>
using namespace std;
const int N = 1010, M = 15;
int n, m;
char a[N][M];
int f[N][N];
int edit_dist(char a[], char b[])
{
int s1 = strlen(a+1), s2 = strlen(b+1);
for( int i = 0; i <= s2; i++ )
f[0][i] = i;
for( int j = 0; j <= s1; j++ )
f[j][0] = j;
for( int i = 1; i <= s1; i++ )
for( int j = 1; j <= s2; j++ ){
f[i][j] = min(f[i-1][j]+1, f[i][j-1]+1);
if(a[i]==b[j])
f[i][j] = f[i-1][j-1];
else
f[i][j] = min(f[i][j], f[i-1][j-1]+1);
}
return f[s1][s2];
}
int main()
{
cin>>n>>m;
for( int i = 1; i <= n; i++ )
cin>>a[i]+1;
while(m--){
char s[N];
int limit;
cin>>s+1>>limit;
int res = 0;
for( int i = 1; i <= n; i++ )
if(edit_dist(a[i], s) <= limit)
res++;
cout<<res<<endl;
}
return 0;
}
本文来自博客园,作者:对影丶成三人,转载请注明原文链接:https://www.cnblogs.com/IntroductionToAlgorithms/p/15361726.html
浙公网安备 33010602011771号