【poj 2127】Greatest Common Increasing Subsequence 最长公共上升子序列lics+路径打印

47 篇文章 0 订阅

题意:给你两个数列,叫你求他们的最长公共子序列,并且打印路径

对于最长子序列的n*m算法,网上多的是,可以具体去找一下,这里只做一个小结:

定义f[i][j]表示对于a数组的第i个为止(包括前面的),然后b数组以j结尾(必须是j),的最长公共上升子序列,很容易想到n3复杂的的算法

if( a[ i ] != b[ j ] ) f[ i ][ j ] = f[ i-1 ] [ j ](参考lcs算法)

if( a[ i ] ==b [ j ]) f[ i ][ j ] = max( f[ i-1 ][ k ]+1) k<j

这里的优化就在于每一次都要去求一个max而事实是在遍历这个数字之前,所有的f[i-1][k]的情况都会访问一次,就会有很多重复,所以每一次定义一个之前最大值,用的时候就可以直接更新了,那么接下来的问题就是什么时候更新这个最大值呢?有一个事实是,只有当a[ i ] == b [ j ]的时候我们才会用到这个最大值,所以呢,每次只要 a[ i ]> b[ j ]就更新,保证后面找到的b[ j ]一定比前面大


最后也是最关键的就是打印路径的问题,之前也做过几个类似的打印路径的题,基本上是dp开几维这就几维,但是一开始只想用一维的,而且样例也是没有错的,过了很多组,但是忽略了一个事实就是后面的数字会更改前继。比如这个数据:

6
3 6 9 12 4 6
5
3 4 6 9 12
就会错,不懂就自己慢慢调试就知道了

正确代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#define clear(x) memset(x,0,sizeof(x))
#define maxn 521
using namespace std;
int n,m,path[maxn][maxn],f[maxn][maxn],a[maxn],b[maxn],ans,q[maxn];



int main(){
	while(~scanf("%d",&n)){
		ans=0;
		int ai,aj;
		clear(f);clear(path);clear(q);
		for(int i=1;i<=n;i++)scanf("%d",a+i);
		scanf("%d",&m);
		for(int i=1;i<=m;i++)scanf("%d",b+i);
		for(int i=1;i<=n;i++){
			int Max=0,mj=0;
			for(int j=1;j<=m;j++){
				f[i][j]=f[i-1][j];path[i][j]=-1;
				if(a[i]>b[j]&&f[i][j]>Max){
					Max=f[i][j],mj=j;
				}
				if(a[i]==b[j]){
					f[i][j]=Max+1;
					path[i][j]=mj;
				}
				if(f[i][j]>ans){
					ans=f[i][j];
					ai=i,aj=j;
				}
			}
		}
		printf("%d\n",ans);
		int tem=ans;
		while(tem){
			if(path[ai][aj]!=-1){
				q[tem--]=b[aj];
				aj=path[ai][aj];
			}
			ai--;
		}
		for(int i=1;i<=ans;i++)printf("%d%c",q[i],i==ans?'\n':' ');
	}
	return 0;
}

bug代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<stack>
#define maxn 1020
using namespace std;
int a[maxn],b[maxn],f[maxn][maxn],n,m,dp[maxn];

stack<int>s;

void front(){
	memset(a,0,sizeof(a));
	memset(b,0,sizeof(b));
	memset(f,0,sizeof(f));
	while(!s.empty())s.pop();
}

int main(){
	while(scanf("%d",&n)!=EOF){
		front();
		for(int i=1;i<=n;i++)scanf("%d",a+i);
		scanf("%d",&m);
		for(int i=1;i<=m;i++)scanf("%d",b+i);
		for(int i=1;i<=n;i++){
			int Max=0;
			for(int j=1;j<=m;j++){
				f[i][j]=f[i-1][j];
				if(a[i]>b[j])Max=max(Max,f[i-1][j]);
				if(a[i]==b[j])f[i][j]=max(f[i][j],Max+1);
			}
		}
		int ans=0,j,last=1e9;
		for(int i=m;i>=1;i--){
			ans=max(ans,f[n][i]);
		}
		int x=ans;
		for(int i=m;i>=1;i--){
			if(f[n][i]==x&&b[i]<last){
				s.push(b[i]);
				x--;last=b[i];
			}
		}
		printf("%d\n",ans);
		if(ans==0)printf("\n");
		else
		while(!s.empty()){
			printf("%d ",s.top());
			s.pop();
		}
		printf("\n");
	}
	
	return 0;
}/*
4
 1 7 6 8
4
 1 7 8 6
*/


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值