一些线性DP题目的の个人总结

本文概述了最长公共子序列问题的洛谷解法,骨王问题(0/1背包第K优解)的代码实现,以及凸n边形不同划分的卡特兰数求解。深入解析了这三个经典计算机科学问题,并提供了对应的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

目录

一、最长公共子序列(洛谷P1439)

 (1)题目分析

(2)代码实现(洛谷50分)

(3)补充:最长上升子序列+输出路径(​)

二、骨王(0/1背包第K优解问题)

(1)题目分析

(2)代码实现

三、凸n边形的不同划分方式(卡特兰数)

(1)题目分析

(2)代码实现


一、最长公共子序列(洛谷P1439

题目描述

给出 1,2,\ldots,n1,2,…,n 的两个排列 P_1P1​ 和 P_2P2​ ,求它们的最长公共子序列。

输入格式

第一行是一个数 nn。

接下来两行,每行为 nn 个数,为自然数 1,2,\ldots,n1,2,…,n 的一个排列。

输出格式

一个数,即最长公共子序列的长度。

输入输出样例

输入 #1复制

5 

3 2 1 4 5
1 2 3 4 5

输出 #1复制

3

说明/提示

  • 对于 50\%50% 的数据, n \le 10^3n≤103;
  • 对于 100\%100% 的数据, n \le 10^5n≤105。

 (1)题目分析

先明确概念: 最长公共子串(Longest Common Substirng)和最长公共子序列(Longest Common Subsequence,LCS)的区别为:子串是串的一个连续的部分,子序列则是从不改变序列的顺序,而从序列中去掉任意的元素而获得新的序列;也就是说,子串中字符的位置必须是连续的,子序列则可以不必连续。  

        对于两段序列找公共部分,我们发现当且仅当两者出现相同部分时可进行更新(废话),这就是更新状态的条件。而我们采用最基本的思路,将dp[i][j]的值表示:对于a串的i位和b串的j位,能组成的最长公共子序列长度

(2)代码实现(洛谷50分)

#include<cstdio>
#include<algorithm>
using namespace std;
int n,p1[100005],p2[100005];
int dp[1005][1005];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&p1[i]);
	for(int i=1;i<=n;i++)
		scanf("%d",&p2[i]);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(p1[i]==p2[j])
				dp[i][j]=max(dp[i-1][j-1]+1,dp[i][j]);
			else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
		}
	}
	printf("%d",dp[n][n]);
	return 0;
} 

(3)补充:最长上升子序列+输出路径(n{}^{2}

         对于一个序列的第i位,它之前如有比它小的j,那这个第i位必然成了max(dp[j]+1,dp[i])。而对于储存路径,需要另设一个数组记录前驱。核心代码实现如下:

//DP部分
for(int i=1;i<=n;i++)
	{
		dp[i]=1;
		from[i]=0;
		for(int j=1;j<i;j++)
		if(a[j]<a[i] && dp[i]<dp[j]+1) //第j位<第i位,可更新
		{
			dp[i]=dp[j]+1;
			from[i]=j;     //记录前驱 
		}
	}
//输出(迭代部分)
int ans=dp[1], pos=1;
	for(int i=1;i<=n;i++)
		if(ans<dp[i])
		{
			ans=dp[i];
			pos=i;
	//要记录最长上升子序列的最后一个元素,方便从末向前迭代输出
		}
	printf("%d",ans);
	output(pos);


void output(int x)
{
	if(!x)return;
	output(from[x]);
	cout<a[x]<<" ";
	//迭代输出 
}

二、骨王(0/1背包第K优解问题)

题目描述

骨王有一个容积为V的袋子,用于收集各种不同的骨头,很显然,对于不同的骨头,每个骨头都有自己的价值和体积。现在告诉你每个骨头的价值和体积,请你计算出骨王能够收集到的第K大的总价值为多少?

注意:两种不同方法得到的相同价值的骨头也算作是同一种情况,即,你的答案将按照第1大,第2大…,第K大的顺序严格递减。

输入格式

第一行输入一个数字T,表示数据的组数。

对于每一组数据,有三行输入:

第一行输入三个整数N,V,K(0<N<=100,0<V<=1000,0<K<=30),分别表示骨头的数量,袋子的容积以及要求的第K大值。

第二行包含N个整数,表示每根骨头的价值。

第三行包含N个整数,表示每根骨头的体积。

价值和体积的范围都在1到10^5这个范围内。

输出格式

对于每一组样例,输出一个答案,表示这组数据能够收集到的第K大值。

样例

样例输入

复制3
5 10 2
1 2 3 4 5
5 4 3 2 1
5 10 12
1 2 3 4 5
5 4 3 2 1
5 10 16
1 2 3 4 5
5 4 3 2 1

样例输出1

复制12
2
0

(1)题目分析

        你能明白这是背包,但就是有一点小操作把你卡住了。这不是0/1背包最优解的问题了,它实际上考察了你对于0/1背包问题的全面理解。想想0/1背包的基本公式:

dp[i][j]=max(dp[i-1][j],dp[i-1][j-cost[i]]+val[i])

        那么问题来了,一个max函数帮我们省了什么——一个if判断句的比较+更新dp值。很显然我们要把这个过程加点东西,把相比较的这两个东西存起来,使两者组成一个有序队列。那不妨我们用a[]来存dp[i-1][j],用b[]来存dp[i-1][j-cost[i]]+val[i],并拍成一维,即dp[j]和dp[j-cost[i]]+val[i]。这时再加一维变成dp[v][k],表示背包容积为v时的第k优解。

(2)代码实现

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std; 
int t,n,v,k,f[1005][35],val[105],cos[105];
int a[105],b[105];
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d%d",&n,&v,&k);
		for(int i=1;i<=n;i++)
			scanf("%d",&val[i]);
		for(int i=1;i<=n;i++)
			scanf("%d",&cos[i]);
		memset(f,0,sizeof(f));
		memset(a,0,sizeof(a));
		memset(b,0,sizeof(b));
		for(int i=1;i<=n;i++)
		{
			for(int j=v;j>=cos[i];j--)
			{
				for(int l=1;l<=k;l++)
				{
					a[l]=f[j][l];
					b[l]=f[j-cos[i]][l]+val[i];
				}
				a[k+1]=-1,b[k+1]=-1;//设置边界
				int x=1,y=1,z=1;//设定起始状态
				while(z<=k&&(a[x]>=0||b[y]>=0))
				{
					if(a[x]>b[y])//每次取二者较大值为优
						f[j][z]=a[x],x++;
					else 
						f[j][z]=b[y],y++;
					if(f[j][z]!=f[j][z-1])//防止第k与第k-1的解相同
						z++;
				}
			}
		}
		printf("%d\n",f[v][k]);
	}
	return 0;
}

三、凸n边形的不同划分方式(卡特兰数)

题目描述

卡特兰数又称卡塔兰数,英文名Catalan number,是组合数学中一个常出现在各种计数问题中出现的数列。以比利时的数学家欧仁·查理·卡塔兰 (1814–1894)的名字来命名。

最初,给卡塔兰数建立的数学模型是:一个凸n边形,通过不相交于n边形内部的对角线,把n边形拆分成若干三角形,不同的拆分数目用hn表示,hn即为Catalan数。例如五边形有如下五种拆分方案(如图),故h5=5。求对于一个任意的凸n边形相应的hn。

avatar

输入格式

一个正整数n,代表凸n边形的边数 (2≤n≤37)

输出格式

一个正整数,凸n边形划分成若干三角形的不同划分方式

样例

样例1输入

复制4

样例1输出

复制2

样例2输入

复制5

样例2输出

复制5

(1)题目分析

        有许多问题都在考察卡特兰数,如P5879 放棋子和 P5879 放棋子以及P1044 [NOIP2003 普及组] 栈都是。我的同学崔子大牛做过有关考察卡特兰数的小结:崔子的总结,可以去看看。卡特兰数有以下几个公式:

 

 

        而我个人比较喜欢这道题的递推证明法,已有SCDN的dalao 解释过了——递推之凸n边形的不同划分方式,我就不再解释,直接上代码。

(2)代码实现

#include<cstdio>
long long a[42]={0,0,1,1};//从0开始,long long!!!
int main(){
    int n;
    scanf("%d",&n);
    if(n==2){  
        printf("0\n"); 
        return 0;
    }
    for(int i=4;i<=n;i++){
        for(int j=2;j<i;j++){
            a[i]+=a[j]*a[i-j+1]; 
        }
    }
    printf("%lld\n",a[n]); 
    return 0;
}

注:本文章为个人学习总结,如有错误还请批评指出去( ̄︶ ̄)↗ 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值