动态规划之最长上升子序列模型

										## LIS问题及其应用

模板
这里给出贪心+二分(n log n),dp(n²)两种做法。

//动态规划O(n²)
#include<iostream>
#include<algorithm>
using namespace std;
int n;
const int MAX=1010;
int a[MAX];
int dp[MAX]; 
int main(){
	cin>>n;
	for (int i=1;i<=n;i++)
		cin>>a[i];

	for (int i=1;i<=n;i++){
		dp[i]=1;
		for (int j=1;j<i;j++)
			if (a[i]>a[j])
				dp[i]=max(dp[i],dp[j]+1);
	}
	int res=0;
	for (int i=1;i<=n;i++)
		res=max(res,dp[i]);
	cout<<res<<endl;
	return 0;
	
}
#include<iostream>
#include<algorithm>
using namespace std;
int n;
const int MAX=10010;
int a[MAX],t[MAX];
int main(){
	cin>>n;
	for (int i=1;i<=n;i++)	cin>>a[i];
	int len=0;
	for (int i=1;i<=n;i++){
		int l=0,r=len;
		while (r>l){	//二分:小于a[i]的最大数,如果没有为其创建新位置 
			int mid=l+r+1>>1;
			if (t[mid]<a[i])	l=mid;
			else	r=mid-1;
		}
		t[r+1]=a[i];
		len=max(len,r+1);
	}
	cout<<len<<endl;
	return 0;
}

双向LIS

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int dp[1001];
int a[1001];
int main(){
	int input;
	int n;
	scanf("%d",&input);
	while (input--){
		int result=0;
		scanf("%d",&n);
		for (int i=1;i<=n;i++){
			scanf("%d",&a[i]);
			
		}
		for (int i=1;i<=n;i++){
			dp[i]=1;
			for (int j=1;j<i;j++){
				if (a[i]>a[j])
					dp[i]=max(dp[i],dp[j]+1);
			}
			result=max(result,dp[i]);
		}
		
		
		for (int i=n;i>=1;i--){
			dp[i]=1;
			for (int j=n;j>i;j--){
				if (a[i]>a[j])
					dp[i]=max(dp[i],dp[j]+1);
			}
			result=max(result,dp[i]);
		}
		printf("%d\n",result);
	}
	return 0;
}

爆搜+LIS

思路:预处理出以每个点为结尾的LIS,和以每个点为开头的下降序列。再通过暴力枚举出每一种情况。

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
int n;
const int MAX=1100;
int high[MAX],higher[MAX],lower[MAX],res=0;
int main(){
	cin>>n;
	for (int i=1;i<=n;i++)
		cin>>high[i];
	for (int i=1;i<=n;i++){
		higher[i]=1;
		for (int j=1;j<i;j++){
			if (high[i]>high[j])
				higher[i]=max(higher[i],higher[j]+1);
		}
	}
	for (int i=n;i;i--){
		lower[i]=1;
		for (int j=i+1;j<=n;j++){
			if (high[j]<high[i])
				lower[i]=max(lower[i],lower[j]+1); 
		} 
	}
	
 	for (int i=1;i<=n;i++)
 		res=max(res,higher[i]+lower[i]-1);
 	cout<<res<<endl;
	return 0;
}

合唱队形(爆搜+LIS)
思路:
要想剔除的人最少,就需要对留下的人最多。根据题意,每个队列都会有一个中间点。(这个点左边的点比他小,右边的点比他大),那么我们先预处理出以每个点作为中间点时,比它小的点的个数和比他大的点的个数。用上一题的做法求出队列中最多剩下的人数之后,再用n(总共的人数)-这个数 就是答案。

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
int n;
const int MAX=110;
int a[MAX];
int higher[MAX],lower[MAX];
int main(){
	cin>>n;
	for (int i=1;i<=n;i++)
		cin>>a[i];
	for (int i=1;i<=n;i++){
		higher[i]=1;
		for (int j=1;j<i;j++)
		 	if (a[i]>a[j])
		 		higher[i]=max(higher[i],higher[j]+1);
	}
	for (int i=n;i;i--){
		lower[i]=1;
		for (int j=i+1;j<=n;j++)
			if (a[j]<a[i])
				lower[i]=max(lower[i],lower[j]+1);
	}
	int res=0;
	for (int i=1;i<=n;i++)
		res=max(res,lower[i]+higher[i]-1);
	cout<<n-res<<endl;
	
	return 0;
}

友好城市 转换题LIS+sort
思路: 本题的做法是先按照其友好城市的坐标sort一遍,再做一遍LIS。 接下来说明为什么要这么做:
1.友好城市之间的线路不能交叉,就代表着两个友好城市的坐标必须同时单调递增。
2.再加上要求最多的城市对数,很容易想到要使用最长上升子序列。既然要两者同时保持单调性(自然可以基于其友好城市对其进行排序,两个城市同时LIS应该也可以)。
3.最后一边LIS就是答案。

//经过这一题,要注意算法题中单调性,无论是条件还是特性都十分重要。

#include<iostream>
#include<algorithm>
using namespace std;
typedef pair<int ,int > PII;
int n;
const int MAX=5100;
int dp[MAX];
PII a[MAX];

int main(){
    cin>>n;
    for (int i=1;i<=n;i++){
        cin>>a[i].first>>a[i].second;
    }
    sort(a+1,a+n+1);
    int res=0;
    for (int i=1;i<=n;i++){
        dp[i]=1;
        for (int j=1;j<i;j++){
            if (a[i].second>a[j].second)
            dp[i]=max(dp[i],dp[j]+1);
        }
        res=max(dp[i],res);
    }
    cout<<res<<endl;
    return 0;
}

LISdp思想运用 最大上升子序列和

//dp的思想和考虑方式和LIS问题一样。
#include<iostream>
#include<algorithm>
using namespace std;
int n;
const int MAX=10010;
int a[MAX],dp[MAX];


int main(){
    cin>>n;
    int res=0;
    for (int i=1;i<=n;i++)    cin>>a[i];
    for (int i=1;i<=n;i++){
        dp[i]=a[i];
        for (int j=1;j<i;j++){
            if (a[i]>a[j])
                dp[i]=max(dp[i],dp[j]+a[i]);
        }
        res=max(res,dp[i]);
    }

    cout<<res<<endl;
    return 0;
}

LIS问题性质的探索 Dilworth定理
//关于LIS问题有一个非常有趣的性质:把整个序列用其下降子序列填满需要的上升子序列数=该序列的最长上升子序列。
(类似,用上升子序列填满就=最长下降子序列长度)

#include<cstdio>	
#include<iostream>
#include<algorithm>
using namespace std;
const int MAX=10010;
int a[MAX],up[MAX],down[MAX];
int main(){
	int idx=1;
	while (cin>>a[idx],a[idx])	idx++;
	int res1=0,res2=0;
	for (int i=1;i<idx;i++){
		up[i]=1,down[i]=1;
		for (int j=1;j<i;j++){
			if (a[i]<=a[j])
				up[i]=max(up[i],up[j]+1);
			else if (a[i]>=a[j])
				down[i]=max(down[i],down[j]+1);
		}
		res1=max(up[i],res1);
		res2=max(res2,down[i]);
	}
		cout<<res1<<endl<<res2<<endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值