首先附上模板:
#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的值,每一种合法的解都必须满足,如果的话就会右交叉,以此转换为最长上升子序列模型
#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的导弹。
每个数两种选择:放到上升序列里和放到下降序列里,时间复杂度,加一个剪枝,如果总序列个数>=之前算出过的序列个数就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;
}
朴素写法时间复杂度为 ,最耗费时间的地方为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;
}