dp入门【血战LIS和LCS】

Part.One 最长上升子序列(LIS)
顾名思义就是一段序列里,问最长上升能有几个数(还是相对简单的)
模板代码:

#include<bits/stdc++.h>
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl '\n'
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
int main()
{
#ifdef Local
	freopen("../input.in", "r", stdin);
    freopen("../output.out", "w", stdout);
#endif
	int n;
	while(cin>>n){
		int a[n+5];
		int dp[n+5];
		rep(i,1,n){
			cin>>a[i];
		}
		int ans=-1;
		rep(i,1,n){
			dp[i]=1;
			rep(j,1,i-1){
				if(a[j]<a[i]){
					dp[i]=max(dp[i],dp[j]+1);
				}
			}
			ans=max(ans,dp[i]);		
		}
		cout<<ans<<endl;
	}
}

Part.Two 最长公共子序列(LCS)
理解了就觉得还是比较easy的
这是一个动态规划的题目。对于可用动态规划求解的问题,一般有两个特征:①最优子结构;②重叠子问题
设 X=(x1,x2,…xn) 和 Y={y1,y2,…ym} 是两个序列,将 X 和 Y 的最长公共子序列记为LCS(X,Y)

找出LCS(X,Y)就是一个最优化问题。因为,我们需要找到X 和 Y中最长的那个公共子序列。而要找X 和 Y的LCS,首先考虑X的最后一个元素和Y的最后一个元素。
最优子结构:
1)如果 xn=ym,即X的最后一个元素与Y的最后一个元素相同,这说明该元素一定位于公共子序列中。因此,现在只需要找:LCS(Xn-1,Ym-1)

LCS(Xn-1,Ym-1)就是原问题的一个子问题。为什么叫子问题?因为它的规模比原问题小。(小一个元素也是小嘛…)

为什么是最优的子问题?因为我们要找的是Xn-1 和 Ym-1 的最长公共子序列啊。。。最长的!!!换句话说,就是最优的那个。(这里的最优就是最长的意思)

2)如果xn != ym,这下要麻烦一点,因为它产生了两个子问题:LCS(Xn-1,Ym) 和 LCS(Xn,Ym-1)

因为序列X 和 序列Y 的最后一个元素不相等嘛,那说明最后一个元素不可能是最长公共子序列中的元素嘛。(都不相等了,怎么公共嘛)。

LCS(Xn-1,Ym)表示:最长公共序列可以在(x1,x2,…x(n-1)) 和 (y1,y2,…yn)中找。

LCS(Xn,Ym-1)表示:最长公共序列可以在(x1,x2,…xn) 和 (y1,y2,…y(n-1))中找。

求解上面两个子问题,得到的公共子序列谁最长,那谁就是 LCS(X,Y)。用数学表示就是:

LCS=max{LCS(Xn-1,Ym),LCS(Xn,Ym-1)}

由于条件 1) 和 2) 考虑到了所有可能的情况。因此,我们成功地把原问题 转化 成了 三个规模更小的子问题。

在这里插入图片描述在这里插入图片描述
通用的一个模板是:

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl '\n'
#define mem(a) memset(a,0,sizeof(a)) 
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
const int N=1000;
char a[N],b[N];
int dp[N][N];
int main()
{
#ifdef Local
	freopen("../input.in", "r", stdin);
    freopen("../output.out", "w", stdout);
#endif
	int lena,lenb;
	while(cin>>a>>b){
		mem(dp);
		lena=strlen(a);
		lenb=strlen(b);
		rep(i,1,lena){
			rep(j,1,lenb){
				if(a[i-1]==b[j-1]){
					dp[i][j]=dp[i-1][j-1]+1;
				}else{
					dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
				}
			}
		}
		cout<<dp[lena][lenb]<<endl;
	}
}

Part three.求最大子序列的和
*这是一道简单的动态规划问题。题目意思即为求最大的子序列和。可将数据储存在一个数组中,然后对其进行处理。思路:数组为dp[n],即有n个数据。先假设max-sum = dp[0],并记录初始位置,末位置,和定义一个变量记录初始位置。采用一个n次的循环。循环内的操作为(如果此时循环变量为 i) : 如果dp[i-1] > 0 ,dp[i] = dp[i] + dp[i-1],(具有记录字符序列和的作用),不然的话,抛弃dp[i-1],更新起始位置。接着如果当前序列和dp[i]>max-sum,则将max-sum = dp[i],并记录初始位置和末位置。
*状态转移方程:dp[i] = MAX(dp[i - 1] + dp[i], dp[i]) (n>=2)

#include<bits/stdc++.h>
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl '\n'
#define mem(a) memset(a,0,sizeof(a))
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
int a[1000005];
int main()
{
#ifdef Local
	freopen("../input.in", "r", stdin);
    freopen("../output.out", "w", stdout);
#endif
	int n,t;
	int Case=0;
	int l,r,maxn;
	int start;
	cin>>t;
	while(t--){
		cin>>n;
		rep(i,0,n-1){
			cin>>a[i];
		}
		start=l=r=0;
		maxn=a[0];
		rep(i,1,n-1){
			if(a[i-1]>=0){
				a[i]+=a[i-1];
			}else{
				start=i;
			}
			if(a[i]>maxn){
				maxn=a[i];
				l=start;
				r=i;
			}
		}
		if(Case){
			printf("\n"); 
		}
		printf("Case %d:\n", ++Case);
        printf("%d %d %d\n", maxn, l+1, r+1);
		
	}
}

Part Four:滚动数组在dp的运用(POJ1159)
题意很明确,给你一个字符串,可在任意位置添加字符,最少再添加几个字符,可以使这个字符串成为回文字符串。

看到这道题,很熟悉,貌似以前做过,可就是想不起来解法,还是以前做的不认真。很惭愧,不过这次的解更优化。

解题思路:老实说,我从来没想到要用动态规划做。或许对动态规划了解不深吧,练得少。如果要是明确求两个字符串的最长公共子序列,我知道要用动态规划,这个老师也讲了多遍。万万没想到这道题就是变形后求原串与其逆串的最长公共子序列,然后用串长减去最长公共子序列的长度就是要添加的最少的字符数。(如果有人提醒我,我就知道怎么办了,可是在比赛的时候全靠自己。)另外还需注意的是字符串长度最长Max为5000,如果用数组maxlen[Max][Max],那么内存会超出。所以引进滚动数组,只需要定义maxlen[2][Max]就可以把问题解决了。(这是从队友那里学来的)首先初始化e=0;在每次行循环中用到e=1-e 就OK了。(这个很好用,大大减少的内存的占用。)

#include<iostream>
#include<algorithm>
#include<cstring>
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl '\n'
#define mem(a) memset(a,0,sizeof(a))
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
char a[1000005];
char b[1000005];
long long dp[2][5005];
int main()
{
#ifdef Local
	freopen("../input.in", "r", stdin);
    freopen("../output.out", "w", stdout);
#endif
	int n;
	while(cin>>n){
		mem(dp);
		for(int i=0;i<n;i++){
			cin>>a[i];
		}
		for(int i=0;i<n;i++){
			b[i]=a[n-1-i];
		}
		int e=0;
		for(int i=1;i<=n;i++){
			e=1-e;//利用滚动数组保证不会爆内存
			for(int j=1;j<=n;j++){
				if(a[i-1]==b[j-1]){
					dp[e][j]=dp[1-e][j-1]+1;
				}else{
					dp[e][j]=max(dp[1-e][j],dp[e][j-1]);
				}
			}
		}
		cout<<n-dp[e][n]<<endl;	
	}
}
发布了71 篇原创文章 · 获赞 5 · 访问量 3417

猜你喜欢

转载自blog.csdn.net/Rainfoo/article/details/102331766