最长上升子序列模型 笔记

4 篇文章 0 订阅

 首先附上模板:

#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
 
using namespace std;
 
typedef pair<int, int> PII;
typedef long long ll;

const int N = 100010;

int n;
int a[N], q[N];

int main()
{
	IOS
	cin >> n;
	for(int i = 1; i <= n; i ++)
	{
		cin >> a[i];
	}
	int len = 0;
	q[0] = -2e9;
	for(int i = 1; i <= n; i ++)
	{
		int l = 0, r = len;
		while(l < r)
		{
			int mid = l + r + 1 >> 1;
			if(q[mid] < a[i])l = mid;
			else r = mid - 1;
		}
		len = max(len, r + 1);
		q[r + 1] = a[i];
	}
	cout << len;
	
	return 0;
}

友好城市

Palmia国有一条横贯东西的大河,河有笔直的南北两岸,岸上各有位置各不相同的N个城市。

北岸的每个城市有且仅有一个友好城市在南岸,而且不同城市的友好城市不相同。

每对友好城市都向政府申请在河上开辟一条直线航道连接两个城市,但是由于河上雾太大,政府决定避免任意两条航道交叉,以避免事故。

编程帮助政府做出一些批准和拒绝申请的决定,使得在保证任意两条航线不相交的情况下,被批准的申请尽量多。

输入格式

第1行,一个整数N,表示城市数。

第2行到第n+1行,每行两个整数,中间用1个空格隔开,分别表示南岸和北岸的一对友好城市的坐标。

输出格式

仅一行,输出一个整数,表示政府所能批准的最多申请数。

数据范围

1≤N≤5000,
0≤xi≤10000

输入样例:
7
22 4
2 6
10 3
15 12
9 8
17 17
4 2
输出样例:
4

可以把下面这行看成下标,上面对应的看成ai的值,每一种合法的解都必须满足a_{i+1}>a_i,如果a_{i+1}<a_i的话就会右交叉,以此转换为最长上升子序列模型

#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
 
using namespace std;
 
typedef pair<int, int> PII;
typedef long long ll;
typedef pair<ll, int> PLI;

const int N = 5010;

int n;
int f[N];
int q[N], len;

int main()
{
	IOS
	cin >> n;
	vector<PII> A;
	for(int i = 0; i < n; i ++)
	{
		int x, y;
		cin >> x >> y;
		A.push_back({x, y});
	}
	sort(A.begin(), A.end());
	
	q[0] = -2e9;
	for(int i = 0; i < n; i ++)
	{
		int x = A[i].second, l = 0, r = len;
		while(l < r)
		{
			int mid = l + r + 1 >> 1;
			if(q[mid] < x)l = mid;
			else r = mid - 1;
		}
		len = max(len, r + 1);
		q[r + 1] = x;
	}
	cout << len;
	
	return 0;
}

拦截导弹

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。

但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。

某天,雷达捕捉到敌国的导弹来袭。

由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数,导弹数不超过1000),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

输入格式

共一行,输入导弹依次飞来的高度。

输出格式

第一行包含一个整数,表示最多能拦截的导弹数。

第二行包含一个整数,表示要拦截所有导弹最少要配备的系统数。

数据范围

雷达给出的高度数据是不大于 30000 的正整数,导弹数不超过 1000。

输入样例:
389 207 155 300 299 170 158 65
输出样例:
6
2

#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
 
using namespace std;
 
typedef pair<int, int> PII;
typedef long long ll;
typedef pair<ll, int> PLI;

const int N = 1010;

int a[N], n;
int q[N], len;

int main()
{
	IOS
	n = 1;
	while(cin >> a[n])n ++;
	n --;
	
	//最长不上升子序列长度=最少上升子序列个数
	q[0] = 2e9;
	for(int i = 1; i <= n; i ++)
	{
		int l = 0, r = len;
		while(l < r)
		{
			int mid = l + r + 1 >> 1;
			if(q[mid] >= a[i])l = mid;
			else r = mid - 1;
		}
		len = max(len, r + 1);
		q[r + 1] = a[i];
	} 
	cout << len << endl;
	
	//最少不上升子序列个数 
	len = 0;//数量
	for(int i = 1; i <= n; i ++)
	{
		int l = 0, r = len;
		while(l < r)
		{
			int mid = l + r + 1 >> 1;
			if(q[mid] < a[i])l = mid;
			else r = mid - 1;
		}
		len = max(len, r + 1);
		q[r + 1] = a[i];
	}
	cout << len;
	
	return 0;
}

求最少不上升子序列个数:

①找到大于等于a[i]的最小数, 将其替换为a[i]

②如果没有大与a[i]的数就新开一个放在后面

明显是一个单调递增的序列

整理出4种求最少子序列个数的方法

1.最长上升子序列长度 = 最少不上升子序列个数

单调递增

找到小于a[i]的最大的数,下一个数就是大于等于a[i]的最小的数

q[0] = -2e9;
for(int i = 1; i <= n; i ++)
{
	int l = 0, r = len;
	while(l < r)
	{
		int mid = l + r + 1 >> 1;
		if(q[mid] < a[i])l = mid;
		else r = mid - 1;
	}
	len = max(len, r + 1);
	q[r + 1] = a[i];
}

2.最长不上升子序列长度 = 最少上升子序列个数

单调递减

小于它的最大的数放

先找>=a[i]的最小的数,下一个数就是小于a[i]的最大的数

q[0] = 2e9;
for(int i = 1; i <= n; i ++)
{
	int l = 0, r = len;
	while(l < r)
	{
		int mid = l + r + 1 >> 1;
		if(q[mid] >= a[i])l = mid;
		else r = mid - 1;
	}
	len = max(len, r + 1);
	q[r + 1] = a[i];
} 

3.最长下降子序列长度 = 最少不下降子序列个数

单调递减

小于等于它的最大的数放

先找大于a[i]的最小的数,下一个数就是小于等于a[i]的最大的数

q[0] = 2e9;
for(int i = 1; i <= n; i ++)
{
	int l = 0, r = len;
	while(l < r)
	{
		int mid = l + r + 1 >> 1;
		if(q[mid] > a[i])l = mid;
		else r = mid - 1;
	}
	len = max(len, r + 1);
	q[r + 1] = a[i];
} 

4.最长不下降子序列长度 = 最少下降子序列个数

单调递增

大于它的最小的数放

找到小于等于a[i]的最大的数,下一个数就是大于a[i]的最小的数

q[0] = -2e9;
for(int i = 1; i <= n; i ++)
{
	int l = 0, r = len;
	while(l < r)
	{
		int mid = l + r + 1 >> 1;
		if(q[mid] <= a[i])l = mid;
		else r = mid - 1;
	}
	len = max(len, r + 1);
	q[r + 1] = a[i];
}

总之求最少子序列个数都可以nlogn的求,求最长不上升子序列长度\最长不下降子序列长度问题还需要转化为子序列个数问题。

导弹防御系统

为了对抗附近恶意国家的威胁,R 国更新了他们的导弹防御系统。

一套防御系统的导弹拦截高度要么一直 严格单调 上升要么一直 严格单调 下降。

例如,一套系统先后拦截了高度为 3 和高度为 4 的两发导弹,那么接下来该系统就只能拦截高度大于 4 的导弹。

给定即将袭来的一系列导弹的高度,请你求出至少需要多少套防御系统,就可以将它们全部击落。

输入格式

输入包含多组测试用例。

对于每个测试用例,第一行包含整数 n,表示来袭导弹数量。

第二行包含 n个不同的整数,表示每个导弹的高度。

当输入测试用例 n=0 时,表示输入终止,且该用例无需处理。

输出格式

对于每个测试用例,输出一个占据一行的整数,表示所需的防御系统数量。

数据范围

1≤n≤50

输入样例:
5
3 5 2 4 1
0 
输出样例:
2
样例解释

对于给出样例,最少需要两套防御系统。

一套击落高度为 3,4 的导弹,另一套击落高度为 5,2,1的导弹。

每个数两种选择:放到上升序列里和放到下降序列里,时间复杂度2^{50},加一个剪枝,如果总序列个数>=之前算出过的序列个数就return。(剪枝后的时间复杂度不会算,反正过了)

#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
 
using namespace std;
 
typedef pair<int, int> PII;
typedef long long ll;
typedef pair<ll, int> PLI;

const int N = 60;

int n;
int a[N];
int up[N], down[N];
int ans;

void dfs(int u, int su, int sd)
{
	if(su + sd >= ans)return;
	if(u == n + 1)
	{
		ans = min(ans, su + sd);
		return;
	}
	
	int l = 0, r = su;
	while(l < r)
	{
		int mid = l + r + 1 >> 1;
		if(up[mid] >= a[u])l = mid;
		else r = mid - 1;
	}
	int t = up[r + 1];
	up[r + 1] = a[u];
	dfs(u + 1, max(su, r + 1), sd);
	up[r + 1] = t;
	
	l = 0, r = sd;
	while(l < r)
	{
		int mid = l + r + 1 >> 1;
		if(down[mid] <= a[u])l = mid;
		else r = mid - 1;
	}
	t = down[r + 1];
	down[r + 1] = a[u];
	dfs(u + 1, su, max(sd, r + 1));
	down[r + 1] = t;
}

int main()
{
	IOS
	up[0] = 2e9, down[0] = -2e9;
	while(cin >> n, n)
	{
		for(int i = 1; i <= n; i ++)cin >> a[i];
		ans = n;
		dfs(1, 0, 0);
		cout << ans << endl;
	}
	
	return 0;
}

最长公共上升子序列

熊大妈的奶牛在小沐沐的熏陶下开始研究信息题目。

小沐沐先让奶牛研究了最长上升子序列,再让他们研究了最长公共子序列,现在又让他们研究最长公共上升子序列了。

小沐沐说,对于两个数列 A 和 B,如果它们都包含一段位置不一定连续的数,且数值是严格递增的,那么称这一段数是两个数列的公共上升子序列,而所有的公共上升子序列中最长的就是最长公共上升子序列了。

奶牛半懂不懂,小沐沐要你来告诉奶牛什么是最长公共上升子序列。

不过,只要告诉奶牛它的长度就可以了。

数列 A 和 B 的长度均不超过 3000。

输入格式

第一行包含一个整数 N,表示数列 A,B 的长度。

第二行包含 N 个整数,表示数列 A。

第三行包含 N 个整数,表示数列 B。

输出格式

输出一个整数,表示最长公共上升子序列的长度。

数据范围

1≤N≤3000,序列中的数字均不超过 2^31−1。

输入样例:
4
2 2 1 3
2 1 2 3
输出样例:
2

 f[i][j]状态表示为A选前i位,B选前j位,且当前为为B[j]的的公共上升子序列长度。//注意不是最长的

集合划分为选A[i]和不选A[i]

选A[i]:即选A[i]又选B[j],且明显都作为最后一位,所以此时前提为A[i]=B[j]

         它可以从前j - 1位中b[k]小于B[j]的所有f[i - 1][k]中选,选f[i-1][k]最大的那个。

不选A[i]:f[i-1][j]

朴素写法:

 

#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
 
using namespace std;
 
typedef pair<int, int> PII;
typedef long long ll;
typedef pair<ll, int> PLI;

const int N = 3010;

int n;
int a[N], b[N];
int f[N][N];

int main()
{
	IOS
	cin >> n;
	for(int i = 1; i <= n; i ++)cin >> a[i];
	for(int i = 1; i <= n; i ++)cin >> b[i];
	
	for(int i = 1; i <= n; i ++)
	{
		for(int j = 1; j <= n; j ++)
		{
			f[i][j] = f[i - 1][j];
			if(a[i] == b[j])
			{
				int v = 0;
				for(int k = 1; k < j; k ++)
				{
					if(b[k] < b[j])
					{
						v = max(v, f[i - 1][k]);
					}
				}
				f[i][j] = max(f[i][j], v + 1);
			}
		}
	}
	int ans = -2e9;
	for(int i = 1; i <= n; i ++)ans = max(ans, f[n][i]);
	cout << ans;
	
	return 0;
}

朴素写法时间复杂度为O(n^3) ,最耗费时间的地方为k的那层循环,代码的优化实质上是对代码的等价变形,既然选A[i]要求A[i]=B[j],那么<B[j]等同于<A[i],在进行j那层循环时就可以进行找的这一步操作。

#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
 
using namespace std;
 
typedef pair<int, int> PII;
typedef long long ll;
typedef pair<ll, int> PLI;

const int N = 3010;

int n;
int a[N], b[N];
int f[N][N];

int main()
{
	IOS
	cin >> n;
	for(int i = 1; i <= n; i ++)cin >> a[i];
	for(int i = 1; i <= n; i ++)cin >> b[i];
	
	for(int i = 1; i <= n; i ++)
	{
	    int v = 0;
		for(int j = 1; j <= n; j ++)
		{
			f[i][j] = f[i - 1][j];
			if(a[i] == b[j])f[i][j] = max(f[i][j], v + 1);
			if(b[j] < a[i])v = max(v, f[i - 1][j]);
		}
	}
	int ans = -2e9;
	for(int i = 1; i <= n; i ++)ans = max(ans, f[n][i]);
	cout << ans;
	
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值