目录
最长上升子序列
【题目描述】
一个数的序列bibi,当b1<b2<...<bSb1<b2<...<bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1,a2,...,aN)(a1,a2,...,aN),我们可以得到一些上升的子序列(ai1,ai2,...,aiK)(ai1,ai2,...,aiK),这里1≤i1<i2<...<iK≤N1≤i1<i2<...<iK≤N。比如,对于序列(1,7,3,5,9,4,8),有它的一些上升子序列,如(1,7),(3,4,8)等等。这些子序列中最长的长度是4,比如子序列(1,3,5,8)。
你的任务,就是对于给定的序列,求出最长上升子序列的长度。
【输入】
输入的第一行是序列的长度N(1≤N≤1000)。第二行给出序列中的N个整数,这些整数的取值范围都在0到10000。
【输出】
最长上升子序列的长度。
【输入样例】
7
1 7 3 5 9 4 8
【输出样例】
4
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int f[N],a[N],g[N];
int main()
{
int n;
cin>>n;
for(int i=0;i<n;i++)
cin>>a[i];
for(int i=0;i<n;i++)
{
f[i]=1;
for(int j=0;j<i;j++)
if(a[j]<a[i])
{
f[i]=max(f[i],f[j]+1);
// if(f[i]<f[j]+1)
// {
// f[i]=f[j]+1;
// g[i]=j;
// }
}
}
// int k = 1;
// for (int i = 1; i <= n; i ++)
// if (f[i] > f[k]) k = i;
// cout << f[k] << endl;
// for (int i = 0, len = f[k]; i < len; i ++)
// {
// cout << a[k] << ' ';
// k = g[k];
// }
int res=0;
for(int i=0;i<n;i++)
res=max(res,f[i]);
cout<<res;
return 0;
}
怪盗基德的滑翔翼
【题目描述】
怪盗基德是一个充满传奇色彩的怪盗,专门以珠宝为目标的超级盗窃犯。而他最为突出的地方,就是他每次都能逃脱中村警部的重重围堵,而这也很大程度上是多亏了他随身携带的便于操作的滑翔翼。
有一天,怪盗基德像往常一样偷走了一颗珍贵的钻石,不料却被柯南小朋友识破了伪装,而他的滑翔翼的动力装置也被柯南踢出的足球破坏了。不得已,怪盗基德只能操作受损的滑翔翼逃脱。
假设城市中一共有N幢建筑排成一条线,每幢建筑的高度各不相同。初始时,怪盗基德可以在任何一幢建筑的顶端。他可以选择一个方向逃跑,但是不能中途改变方向(因为中森警部会在后面追击)。因为滑翔翼动力装置受损,他只能往下滑行(即:只能从较高的建筑滑翔到较低的建筑)。他希望尽可能多地经过不同建筑的顶部,这样可以减缓下降时的冲击力,减少受伤的可能性。请问,他最多可以经过多少幢不同建筑的顶部(包含初始时的建筑)?
【输入】
输入数据第一行是一个整数K(K<100) ,代表有KK组测试数据。
每组测试数据包含两行:第一行是一个整数N(N<100)N(N<100),代表有N幢建筑。第二行包含NN个不同的整数,每一个对应一幢建筑的高度h(0<h<10000),按照建筑的排列顺序给出。
【输出】
对于每一组测试数据,输出一行,包含一个整数,代表怪盗基德最多可以经过的建筑数量。
【输入样例】
3
8
300 207 155 299 298 170 158 65
8
65 158 170 298 299 155 207 300
10
2 1 3 4 5 6 7 8 9 10
【输出样例】
6
6
9
#include<bits/stdc++.h>
using namespace std;
const int N=1010,INF=0x3f3f3f3f;
int a[N],f[N],g[N];
int main()
{
int t;
cin>>t;
while(t--)
{
int n;
cin>>n;
memset(f,0,sizeof f);
memset(g,0,sizeof g);
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<=n;i++)
{
f[i]=1;
for(int j=1;j<=i;j++)
if(a[j]<a[i])
f[i]=max(f[i],f[j]+1);
}
for(int i=n;i>=1;i--)
{
g[i]=1;
for(int j=n;j>=i;j--)
if(a[j]<a[i])
g[i]=max(g[i],g[j]+1);
}
for(int i=1;i<=n;i++)
f[i]=max(f[i],g[i]);
int res=-INF;
for(int i=1;i<=n;i++)
res=max(res,f[i]);
cout<<res<<endl;
}
}
登山
【题目描述】
五一到了,ACM队组织大家去登山观光,队员们发现山上一共有N个景点,并且决定按照顺序来浏览这些景点,即每次所浏览景点的编号都要大于前一个浏览景点的编号。同时队员们还有另一个登山习惯,就是不连续浏览海拔相同的两个景点,并且一旦开始下山,就不再向上走了。队员们希望在满足上面条件的同时,尽可能多的浏览景点,你能帮他们找出最多可能浏览的景点数么?
【输入】
第一行:N (2 <= N <= 1000) 景点数;
第二行:N个整数,每个景点的海拔。
【输出】
最多能浏览的景点数。
【输入样例】
8
186 186 150 200 160 130 197 220
【输出样例】
4
#include<bits/stdc++.h>
using namespace std;
const int N=1010,INF=0x3f3f3f3f;
int a[N],f[N],g[N];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<=n;i++)
{
f[i]=1;
for(int j=1;j<=i;j++)
if(a[j]<a[i])
f[i]=max(f[i],f[j]+1);
}
for(int i=n;i>=1;i--)
{
g[i]=1;
for(int j=n;j>=i;j--)
if(a[j]<a[i])
g[i]=max(g[i],g[j]+1);
}
int res=-INF;
for(int i=1;i<=n;i++)
res=max(res,f[i]+g[i]-1);
cout<<res<<endl;
}
合唱队形
【题目描述】
NN位同学站成一排,音乐老师要请其中的(N−K)位同学出列,使得剩下的KK位同学排成合唱队形。
合唱队形是指这样的一种队形:设KK位同学从左到右依次编号为1,2,…,K,他们的身高分别为T1,T2,…,TK,则他们的身高满足T1<T2<…<Ti,Ti>Ti+1>…>TK(1≤i≤K)。
你的任务是,已知所有NN位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
【输入】
输入的第一行是一个整数N(2≤N≤100)N,表示同学的总数。第二行有nn个整数,用空格分隔,第ii个整数Ti(130≤Ti≤230)是第i位同学的身高(厘米)。
【输出】
输出包括一行,这一行只包含一个整数,就是最少需要几位同学出列。
【输入样例】
8
186 186 150 200 160 130 197 220
【输出样例】
4
和登山一样,需要改一下结果,要取出人
#include<bits/stdc++.h>
using namespace std;
const int N=1010,INF=0x3f3f3f3f;
int a[N],f[N],g[N];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<=n;i++)
{
f[i]=1;
for(int j=1;j<=i;j++)
if(a[j]<a[i])
f[i]=max(f[i],f[j]+1);
}
for(int i=n;i>=1;i--)
{
g[i]=1;
for(int j=n;j>=i;j--)
if(a[j]<a[i])
g[i]=max(g[i],g[j]+1);
}
int res=-INF;
for(int i=1;i<=n;i++)
res=max(res,f[i]+g[i]-1);
cout<<n-res<<endl;
}
友好城市
【题目描述】
Palmia国有一条横贯东西的大河,河有笔直的南北两岸,岸上各有位置各不相同的N个城市。北岸的每个城市有且仅有一个友好城市在南岸,而且不同城市的友好城市不相同。
每对友好城市都向政府申请在河上开辟一条直线航道连接两个城市,但是由于河上雾太大,政府决定避免任意两条航道交叉,以避免事故。编程帮助政府做出一些批准和拒绝申请的决定,使得在保证任意两条航线不相交的情况下,被批准的申请尽量多。
【输入】
第1行,一个整数N(1≤N≤5000),表示城市数。
第2行到第n+1行,每行两个整数,中间用1个空格隔开,分别表示南岸和北岸的一对友好城市的坐标。(0<≤xi≤10000)
【输出】
仅一行,输出一个整数,表示政府所能批准的最多申请数。
【输入样例】
7
22 4
2 6
10 3
15 12
9 8
17 17
4 2
【输出样例】
4
#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
const int N=5010;
typedef pair<int,int> PII;
int f[N];
PII p[N];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>p[i].x>>p[i].y;
}
sort(p+1,p+n+1);
for(int i=1;i<=n;i++)
{
f[i]=1;
for(int j=1;j<i;j++)
{
if(p[i].y>p[j].y)f[i]=max(f[i],f[j]+1);
}
}
int res=0;
for(int i=1;i<=n;i++)
res=max(f[i],res);
cout<<res<<endl;
}
拦截导弹
【题目描述】
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数,导弹数不超过1000),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
【输入】
输入导弹依次飞来的高度。
【输出】
第一行:最多能拦截的导弹数;
第二行:要拦截所有导弹最少要配备的系统数。
【输入样例】
389 207 155 300 299 170 158 65
【输出样例】
6
2
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int f[N],a[N];
bool vis[N];
int main()
{
int n=0;
int x;
while(cin>>x)a[++n]=x;
int res=0;
//求最长不上升子序列
for(int i=n;i>=1;i--)
{
f[i]=1;
for(int j=n;j>i;j--)
if(a[i]>=a[j])
f[i]=max(f[i],f[j]+1);
res=max(res,f[i]);
}
cout<<res<<endl;
//求数组最少能被几个最长下降子序列全部覆盖
int cnt=0,h=0,ans=0; //cnt是统计已经拦截的导弹
//h每次拦截后的高度
//ans最少导弹拦截系统
while(cnt<n)
{
ans++;
h=30000;
for(int i=1;i<=n;i++)
{
if(h>=a[i]&&!vis[i])
{
h=a[i]; //更新高度
vis[i]=1; //被访问过
cnt++;
}
}
}
cout<<ans<<endl;
}
导弹防御系统
导弹防御系统【剪枝:迭代加深/全局最小值】
为了对抗附近恶意国家的威胁,RR 国更新了他们的导弹防御系统。
一套防御系统的导弹拦截高度要么一直 严格单调 上升要么一直 严格单调 下降。
例如,一套系统先后拦截了高度为 3 和高度为 4 的两发导弹,那么接下来该系统就只能拦截高度大于 44 的导弹。
给定即将袭来的一系列导弹的高度,请你求出至少需要多少套防御系统,就可以将它们全部击落。
输入格式
输入包含多组测试用例。
对于每个测试用例,第一行包含整数 nn,表示来袭导弹数量。
第二行包含 n 个不同的整数,表示每个导弹的高度。
当输入测试用例 n=0时,表示输入终止,且该用例无需处理。
输出格式
对于每个测试用例,输出一个占据一行的整数,表示所需的防御系统数量。
数据范围
1≤n≤50
输入样例:
5
3 5 2 4 1
0
输出样例:
2
样例解释
对于给出样例,最少需要两套防御系统。
一套击落高度为 3,4 的导弹,另一套击落高度为 5,2,1 的导弹。
#include <iostream>
using namespace std;
// 这题是拦截拦截第二问的加强版
// 拦截导弹第二问是只考虑下降序列的方案数,因此直接用贪心搜出最优解即可(证明在那一题的笔记里)
// 这一题,确实要考虑下降和上升两种序列的方案数
// 因此只能用dfs进行爆搜两种方案的搭配,但无论是上升还是下降方案,依然采用上一题的贪心思路
const int N = 55;
int n;
int a[N];
int up[N], down[N];
int res;
//三个参数分别是考虑前u个导弹
//已经采用了上升系统个数sum_up
//和下降系统个数sum_down
void dfs(int u, int sum_up, int sum_down) {
//如果已经超过了最优解答案,那么直接剪枝
if (sum_up + sum_down >= res) return;
//没有超过最优解答案,且把所有导弹都考虑到了
//那他就是当前最优解了
if (u == n) {
res = sum_up + sum_down;
return;
}
//情况一:考虑用上升拦截系统来拦截第u个导弹
// 上升拦截系统的贪心思路是:
// 如果当前已有的上升拦截系统的高度都大于第u个导弹高度,则重新开一套系统
// 否则,则由当前低于第u个导弹最高拦截系统来负责拦截
int k = 0;
while (k < sum_up && up[k] >= a[u]) ++k;
//找到了有这么个拦截系统
int t = up[k]; //t用于dfs回溯的时候恢复现场
up[k] = a[u];
if (k >= sum_up) dfs(u + 1, sum_up + 1, sum_down);
else dfs(u + 1, sum_up, sum_down);
//恢复现场
up[k] = t;
//情况二:考虑用下降拦截系统来拦截第u个导弹
// 下降拦截系统的贪心思路是:
// 如果当前已有的下降拦截系统的高度都小于第u个导弹高度,则重新开一套系统
// 否则,则由当前大于第u个导弹最低拦截系统来负责拦截
k = 0;
while (k < sum_down && down[k] <= a[u]) ++k;
t = down[k]; //t用于dfs回溯的时候恢复现场
down[k] = a[u];
if (k >= sum_down) dfs(u + 1, sum_up, sum_down + 1);
else dfs(u + 1, sum_up, sum_down);
//恢复现场
down[k] = t;
}
int main() {
while (cin >> n, n) {
for (int i = 0; i < n; ++i) cin >> a[i];
//最差情况是n个导弹分别用n个系统拦截
//因此可以设置res初始为n来设立哨兵
res = n;
dfs(0, 0, 0);
cout << res << endl;
}
return 0;
}
最长公共上升子序列
熊大妈的奶牛在小沐沐的熏陶下开始研究信息题目。
小沐沐先让奶牛研究了最长上升子序列,再让他们研究了最长公共子序列,现在又让他们研究最长公共上升子序列了。
小沐沐说,对于两个数列 A 和 B,如果它们都包含一段位置不一定连续的数,且数值是严格递增的,那么称这一段数是两个数列的公共上升子序列,而所有的公共上升子序列中最长的就是最长公共上升子序列了。
奶牛半懂不懂,小沐沐要你来告诉奶牛什么是最长公共上升子序列。
不过,只要告诉奶牛它的长度就可以了。
数列 A 和 B 的长度均不超过 3000。
输入格式
第一行包含一个整数 N,表示数列 A,B 的长度。
第二行包含 N 个整数,表示数列 A。
第三行包含 N 个整数,表示数列 B。
输出格式
输出一个整数,表示最长公共上升子序列的长度。
数据范围
1≤N≤3000,序列中的数字均不超过 231−1。
输入样例:
4
2 2 1 3
2 1 2 3
输出样例:
2
朴素版(容易TLE)
#include<bits/stdc++.h>
using namespace std;
const int N=3010;
int a[N],b[N],f[N][N];
int main()
{
int n,ans=0;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) scanf("%d",&b[i]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
f[i][j]=f[i-1][j];//所有不包含a[i]的最长公共上升子序列
if(a[i]==b[j])//所有包含a[i]的最长公共上升子序列
{
f[i][j]=max(f[i][j],1);//当空的时候是自己,也就是1
for(int k=1;k<j;k++)//求最长公共上升子序列
if(b[k]<b[j])
f[i][j]=max(f[i][j],f[i][k]+1);
}
}
//求最大值,第一个序列的前n个字母,与b的匹配最大
for(int i=1;i<=n;i++) ans=max(ans,f[n][i]);
printf("%d\n",ans);
return 0;
}
优化
#include<bits/stdc++.h>
using namespace std;
const int N=3010;
int a[N],b[N],f[N][N];
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) scanf("%d",&b[i]);
for(int i=1;i<=n;i++)
{
int maxv=1;//由于在k中重复求前j个最大值,所有用一个maxv记录前j个的最长上升子序列的最大值
for(int j=1;j<=n;j++)
{
f[i][j]=f[i-1][j];//所有不包含a[i]的最长公共上升子序列
if(a[i]==b[j]) f[i][j]=max(f[i][j],maxv);//所有包含a[i]的最长公共上升子序列,更新最大值
if(b[j]<a[i]) maxv=max(maxf,f[i][j]+1);//因为a[i]==b[j]的时候更新最大值,所以可以把b[j]换成a[i],用来与新的b[j]比较
}
}
//最后更新一遍最大值
int ans=0;
for(int i=1;i<=n;i++) ans=max(ans,f[n][i]);
printf("%d\n",ans);
return 0;
}