目录
一、最长公共子序列(洛谷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)补充:最长上升子序列+输出路径(
)
对于一个序列的第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。
输入格式
一个正整数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;
}
注:本文章为个人学习总结,如有错误还请批评指出去( ̄︶ ̄)↗