[NOIP2004 提高组] 合唱队形
题目描述
n n n 位同学站成一排,音乐老师要请其中的 n − k n-k n−k 位同学出列,使得剩下的 k k k 位同学排成合唱队形。
合唱队形是指这样的一种队形:设 k k k 位同学从左到右依次编号为 1 , 2 , 1,2, 1,2, … , k ,k ,k,他们的身高分别为 t 1 , t 2 , t_1,t_2, t1,t2, … , t k ,t_k ,tk,则他们的身高满足 t 1 < ⋯ < t i > t i + 1 > t_1< \cdots <t_i>t_{i+1}> t1<⋯<ti>ti+1> … > t k ( 1 ≤ i ≤ k ) >t_k(1\le i\le k) >tk(1≤i≤k)。
你的任务是,已知所有 n n n 位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
输入格式
共二行。
第一行是一个整数 n n n( 2 ≤ n ≤ 100 2\le n\le100 2≤n≤100),表示同学的总数。
第二行有 n n n 个整数,用空格分隔,第 i i i 个整数 t i t_i ti( 130 ≤ t i ≤ 230 130\le t_i\le230 130≤ti≤230)是第 i i i 位同学的身高(厘米)。
输出格式
一个整数,最少需要几位同学出列。
样例 #1
样例输入 #1
8
186 186 150 200 160 130 197 220
样例输出 #1
4
提示
对于 50 % 50\% 50% 的数据,保证有 n ≤ 20 n \le 20 n≤20。
对于全部的数据,保证有 n ≤ 100 n \le 100 n≤100。
#include <bits/stdc++.h>
using namespace std;
int n, ans;
int a[105];
int l[105], r[105]; //从左开始的LIS和从右开始的LIS
int f[105]; //f[i] 表示以第i个同学为最高点,队列长度
int main()
{
cin >> n;
for(int i = 1; i <= n; i ++) cin >> a[i];
for(int i = 1; i <= n; i ++) {
l[i] = 1;
for(int j = 1; j < i; j ++) {
if(a[j] < a[i]) l[i] = max(l[i], l[j] + 1);
}
}
for(int i = n; i; i --) {
r[i] = 1;
for(int j = n; j > i; j --) {
if(a[j] < a[i]) r[i] = max(r[i], r[j] + 1);
}
}
for(int i = 1; i <= n; i ++) {
f[i] = l[i] + r[i] - 1;
ans = max(ans, f[i]);
}
cout << n - ans;
return 0;
}
友好城市
题目描述
有一条横贯东西的大河,河有笔直的南北两岸,岸上各有位置各不相同的N个城市。北岸的每个城市有且仅有一个友好城市在南岸,而且不同城市的友好城市不相同。每对友好城市都向政府申请在河上开辟一条直线航道连接两个城市,但是由于河上雾太大,政府决定避免任意两条航道交叉,以避免事故。编程帮助政府做出一些批准和拒绝申请的决定,使得在保证任意两条航道不相交的情况下,被批准的申请尽量多。
输入格式
第1行,一个整数N,表示城市数。
第2行到第n+1行,每行两个整数,中间用一个空格隔开,分别表示南岸和北岸的一对友好城市的坐标。
输出格式
仅一行,输出一个整数,表示政府所能批准的最多申请数。
样例 #1
样例输入 #1
7
22 4
2 6
10 3
15 12
9 8
17 17
4 2
样例输出 #1
4
提示
50% 1<=N<=5000,0<=xi<=10000
100% 1<=N<=2e5,0<=xi<=1e6
这题需要nlogn的时间来做,所以开一个tail数组记录每个长度下的LIS的尾巴最小值,用到了二分,这里再介绍一下几个二分的函数
**lower_bound( )和upper_bound( )都是利用二分查找的方法在一个排好序的数组中进行查找的。
在从小到大的排序数组中,
lower_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
upper_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
在从大到小的排序数组中,重载lower_bound()和upper_bound()
lower_bound( begin,end,num,greater() ):从数组的begin位置到end-1位置二分查找第一个小于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
upper_bound( begin,end,num,greater() ):从数组的begin位置到end-1位置二分查找第一个小于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。**
#include <bits/stdc++.h>
using namespace std;
int n;
int tail[200005]; //记录长度为i的LIS中,所有末尾数字的最小值
struct Link //a,b表示一对友好城市
{
int a;
int b;
}link[200005];
bool cmp(Link A, Link B)
{
return A.b < B.b;
}//按照一边岸上的位置升序, 桥要不相交,另一边岸上只能取单调上升的坐标
int main()
{
cin >> n;
for(int i = 1; i <= n; i ++) {
cin >> link[i].a >> link[i].b;
}
sort(link + 1, link + n + 1, cmp);
int len = 0;
tail[++len] = link[1].a; //长度为1的LIS
//接下来就是LIS问题
//找link[i].a序列中的最长上升子序列
for(int i = 1; i <= n; i ++) {
if(tail[len] < link[i].a)
{
len++;
tail[len] = link[i].a;
}
else
{
int j = lower_bound(tail + 1, tail + 1 + len, link[i].a) - tail;
tail[j] = link[i].a; //二分出大于等于link[i].a的最小下标
}
}
cout << len;
return 0;
}
[NOIP1999 普及组] 导弹拦截
题目描述
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度,计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
输入格式
一行,若干个整数,中间由空格隔开。
输出格式
两行,每行一个整数,第一个数字表示这套系统最多能拦截多少导弹,第二个数字表示如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
样例 #1
样例输入 #1
389 207 155 300 299 170 158 65
样例输出 #1
6
2
提示
对于前
50
%
50\%
50% 数据(NOIP 原题数据),满足导弹的个数不超过
1
0
4
10^4
104 个。该部分数据总分共
100
100
100 分。可使用
O
(
n
2
)
\mathcal O(n^2)
O(n2) 做法通过。
对于后
50
%
50\%
50% 的数据,满足导弹的个数不超过
1
0
5
10^5
105 个。该部分数据总分也为
100
100
100 分。请使用
O
(
n
log
n
)
\mathcal O(n\log n)
O(nlogn) 做法通过。
对于全部数据,满足导弹的高度为正整数,且不超过 5 × 1 0 4 5\times 10^4 5×104。
此外本题开启 spj,每点两问,按问给分。
upd 2022.8.24 \text{upd 2022.8.24} upd 2022.8.24:新增加一组 Hack 数据。
**Dilworth 定理
将一个序列剖成若干个单调不升子序列的最小个数等于该序列最长上升子序列的个数
**
#include <bits/stdc++.h>
using namespace std;
int n, x;
int a[100005];
int tail[100005];
int tail_[100005];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
while(cin >> x)
{
a[++n] = x;
}
//比LIS复杂一些
//第一问求最长不上升子序列,要把初值赋为无穷哦
memset(tail, 0x7fffffff, sizeof tail);
int len = 0; //用nlogn做法
tail[++len] = a[1];
for(int i = 2; i <= n; i ++) {
if(a[i] <= tail[len])
{
len ++;
tail[len] = a[i];
}
else
{//upper_bound默认是找第一个严格大于,但是第三个参数用greater就是找小于
int j = upper_bound(tail + 1, tail + 1 + len, a[i], greater<int>()) - tail;
tail[j] = a[i];
}
}
cout << len << endl;
//第二问贪心,反向扫描数组,每次把导弹接到尾巴最大的地方
//或者用Dilworth 定理
//将一个序列剖成若干个单调不升子序列的最小个数等于该序列最长上升子序列的个数
//就是一个LIS
int cnt = 0;
tail_[++cnt] = a[1];
for(int i = 2; i <= n; i ++) {
if(a[i] > tail_[cnt])
{
cnt ++;
tail_[cnt] = a[i];
}
else
{//找到第一个大于等于它的数
int j = lower_bound(tail_ + 1, tail_ + 1 + cnt, a[i]) - tail_;
tail_[j] = a[i];
}
}
cout << cnt;
return 0;
}