动态规划的经典问题,这里的讲解网上有很多非常好的,而我却还没有能力写出完整详细的理解,就附图两张吧,这两个图貌似很火,出处找不到了。
LIS
LCS
动态规划的三种形式:
1.记忆递归型
2.“我为人人”递推型
3.“人人为我”递推型
递推并不是动态规划的本质,如何拆分才是核心。拆分成若干子问题.
这里看到知乎上讲解特别好的
动态规划的本质不在于是递推或是递归,也不需要纠结是不是内存换时间
实际问题的解决
这里附三种解法:
递归
/*
вснйлБё╨
в╢л╛
*/
#include<iostream>
#include<cstdio>
using namespace std;
const int INF = 99999999;
const int N = 105;
int a[N];//,dp[N][N];
int Sum(int m,int n)
{
if(m == 0)
return 1;
if(n <= 0)
return 0;
return Sum(m,n-1) + Sum(m-a[n],n-1);
}
int main()
{
int n;
while(~scanf("%d",&n))
{
for(int i = 1;i <= n;i++)
scanf("%d",&a[i]);
cout<< Sum(40,n) <<endl;
}
return 0;
}
“我为人人”递推型
#include<iostream>//“我为人人”递推型动归
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1005;
int a[N],maxnum[N];
int main()
{
int n;
cin >> n;
for(int i = 1;i <= n;i++)
{
scanf("%d",&a[i]);
maxnum[i] = 1;//初始化,最初状态
}
for(int i = 2;i <= n;i++)//每次以第i个数为终点的序列的最长长度
{
for(int j = i+1;j <= n;j++)//查看i为终点中的j为终点上升序列
if(a[j] > a[i])//进行增长
maxnum[j] = max(maxnum[j],maxnum[i] +1);
}
cout<< *max_element(maxnum+1,maxnum+n+1) <<endl;//这个函数很奇妙
}
3.“人人为我”递推型
#include<iostream>//”人人为我“型递推
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1005;
int a[N],maxnum[N];
int main()
{
int n;
cin >> n;
for(int i = 1;i <= n;i++)
{
scanf("%d",&a[i]);
maxnum[i] = 1;//初始化,最初状态
}
for(int i = 2;i <= n;i++)//每次以第i个数为终点的序列的最长长度
{
for(int j = 1;j < i;j++)//查看i为终点中的j为终点上升序列
if(a[i] > a[j])//进行增长
maxnum[i] = max(maxnum[i],maxnum[j] +1);
}
cout<< *max_element(maxnum+1,maxnum+n+1) <<endl;//这个函数很奇妙
}
/*
最长上升子序列
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
char str[10005];
int dp[10005];
int main()
{
int t;
cin>>t;
while(t--)
{
scanf("%s",str);
memset(dp,0,sizeof(dp));
int len = strlen(str);
//LIS
for(int i = 0;i <len;i++)
{
dp[i] = 1;
for(int j = 0; j < i;j++)
if(str[i] > str[j] && dp[i] < dp[j] + 1)
dp[i] = dp[j] + 1;
}
int maxn = -1;
for(int i = 0;i < len;i++)
maxn = max(maxn,dp[i]);
printf("%d\n",maxn);
}
return 0;
}
这里LIS还有经典的拦截炮弹问题
/*
拦截导弹
这类问题还真不少,让求非增最长子序列,只需要按照
单调递增子序列的算法修改为a[i] < a[j]就行了
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int a[30],dp[30];
int main()
{
int t,n;
cin >> t;
while(t--)
{
cin >> n;
for(int i = 1;i <= n;i++)
scanf("%d",&a[i]);
memset(dp,0,sizeof(dp));
//LIS,这个是单调非增
for(int i = 1;i <= n;i++)
{
dp[i] = 1;//最小为1
for(int j = 1;j < i;j++)//以后每一发炮弹都不能高于前一发的高度
if(a[i] < a[j] && dp[i] < dp[j] + 1)
dp[i] = dp[j] + 1;
}
int maxn = 0;
for(int i = 1;i <= n;i++)
maxn = max(maxn,dp[i]);
printf("%d\n",maxn);
}
return 0;
}
//nnd,提交了两次样例,rita,气煞我了
拦截炮弹2
NYOJ36最长公共子序列 LCS
//根据下标不同,有两种写法
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
char s1[1005],s2[1005];
int dp[1005][1005];
int main()
{
int t;
cin >> t;
while(t--)
{
scanf("%s%s",s1,s2);
memset(dp,0,sizeof(dp));
int len1 = strlen(s1);
int len2 = strlen(s2);
for(int i = 0;i < len1;i++)//前i个,前j个
{
for(int j = 0;j < len2;j++)
{
if(s1[i] == s2[j])
dp[i+1][j+1] = dp[i][j] + 1;
else
dp[i+1][j+1] = max(dp[i+1][j],dp[i][j+1]);
}
}
printf("%d\n",dp[len1][len2]);//前len1个字符和前len2个字符
}
return 0;
}
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
char s1[1005],s2[1005];
int dp[1005][1005];
int main()
{
int t;
cin >> t;
while(t--)
{
scanf("%s%s",s1+1,s2+1);
memset(dp,0,sizeof(dp));
int len1 = strlen(s1+1);
int len2 = strlen(s2+1);
for(int i = 1;i <= len1;i++)//前i个,前j个
{
for(int j = 1;j <= len2;j++)
{
if(s1[i] == s2[j])
dp[i][j] = dp[i-1][j-1] + 1;
else
dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
}
}
printf("%d\n",dp[len1][len2]);//前len1个字符和前len2个字符
}
return 0;
}
因为数据量非常大,n^2铁定超时,所以要用二分的dp,讲解见于Here,我是看的这篇讲解
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 100005;
int a[N],dp[N];
int Binarysearch(int k,int right)
{
int left = 1,mid = (right+left)/2;
while(left <= right)
{
if(k > dp[mid])
left = mid + 1;
else if(k < dp[mid])
right = mid - 1;
else
return mid;
mid = (right+left)/2;
}
return left;
}
int main()
{
int n;
while(~scanf("%d",&n))
{
for(int i = 1;i <= n;i++)
scanf("%d",&a[i]);
int len = 1,pos;
dp[1] = a[1];
for(int i = 2;i <= n;i++)
{
pos = Binarysearch(a[i],len);
dp[pos] = a[i];
if(pos > len) len = pos;
}
printf("%d\n",len);
}
return 0;
}
但是按照其原理这样实现更好理解(差不多)
for(int i = 2;i <= n;i++)
{
if(a[i] > dp[len])
dp[++len] = a[i];
else
{
pos = Binarysearch(a[i],len);
dp[pos] = a[i];
}
}
为啥lower_bound不可以呢,求大牛告知