摘要
895.
最长上升子序列 - AcWing题库:LIS模板题(DP)(O(N^2))
896.
最长上升子序列 II - AcWing题库:LIS模板题(贪心 +
二分)O(N*LogN)
1017.
怪盗基德的滑翔翼 - AcWing题库:LIS二次遍历,求max(上升,下降)
1014. 登山 -
AcWing题库:求 先上升再下降 的最大长度
482. 合唱队形 -
AcWing题库:
1014的变形,求最少删去多少个数,可以构成先上升再下降的序列
1012. 友好城市
- AcWing题库:两个序列的LIS,一个作为下标,一个作为数值
1016.
最大上升子序列和 - AcWing题库:求最大和,只需要将 dp[j] + 1 改为
dp[j] + a[i](注意最大和长度可能为1)
1010. 拦截导弹
- AcWing题库:覆盖整个序列的最少的不上升子序列的个数 ==
最长上升子序列的长度
187.
导弹防御系统 - AcWing题库:覆盖整个序列的最少的子序列个数(上升 或
下降)的个数 -> DFS
272.
最长公共上升子序列 -
AcWing题库:LCIS模板题,O(N^2)解法,前缀最大值优化
题谱
895 最长上升子序列
给定一个长度为 N
的数列,求数值严格单调递增的子序列的长度最长是多少。
输入格式
第一行包含整数 N。
第二行包含 N个整数,表示完整序列。
输出格式
输出一个整数,表示最大长度。
数据范围
1≤N≤1000 −109≤数列中的数≤109
输入样例:
输出样例
题解
动态规划O(N^2)
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include<bits/stdc++.h> using namespace std; const int maxn = 1e4;
int n,res; int a[maxn],dp[maxn];
int main(void){ cin>>n; for(int i = 1 ; i <= n ; i ++){ cin>>a[i]; } for(int i = 2 ; i <= n ; i ++){ for(int j = 1 ; j < i ; j ++){ if(a[i] > a[j]){ dp[i] = max(dp[i], dp[j] + 1); res = max(res, dp[i]); } } } cout<<res+1; return 0; }
|
896 最长上升子序列 II
给定一个长度为 N
的数列,求数值严格单调递增的子序列的长度最长是多少。
输入格式
第一行包含整数 N。
第二行包含 N 个整数,表示完整序列。
输出格式
输出一个整数,表示最大长度。
数据范围
1≤N≤100000 −10^9≤数列中的数≤10^9
输入样例
输出样例
题解
二分+贪心O(N*logN)
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| #include<bits/stdc++.h> using namespace std; const int maxn = 1e4;
int n,cnt; int low[maxn],a[maxn];
int binarySearch(int l, int r, int x){ while(l+1 != r){ int mid = (l+r)/2; if(low[mid] < x){ l = mid; }else{ r = mid; } } return r; }
int main(void){ cin>>n; for(int i = 1 ; i <= n ; i ++){ cin>>a[i]; } low[++cnt] = a[1]; for(int i = 2 ; i <= n ; i ++){ if(a[i] > low[cnt]){ low[++cnt] = a[i]; }else{ int j = binarySearch(0, cnt, a[i]); low[j] = a[i]; } } cout<<cnt; return 0; }
|
1017 怪盗基德的滑翔翼
怪盗基德是一个充满传奇色彩的怪盗,专门以珠宝为目标的超级盗窃犯。
而他最为突出的地方,就是他每次都能逃脱中村警部的重重围堵,而这也很大程度上是多亏了他随身携带的便于操作的滑翔翼。
有一天,怪盗基德像往常一样偷走了一颗珍贵的钻石,不料却被柯南小朋友识破了伪装,而他的滑翔翼的动力装置也被柯南踢出的足球破坏了。
不得已,怪盗基德只能操作受损的滑翔翼逃脱。
假设城市中一共有N幢建筑排成一条线,每幢建筑的高度各不相同。
初始时,怪盗基德可以在任何一幢建筑的顶端。
他可以选择一个方向逃跑,但是不能中途改变方向(因为中森警部会在后面追击)。
因为滑翔翼动力装置受损,他只能往下滑行(即:只能从较高的建筑滑翔到较低的建筑)。
他希望尽可能多地经过不同建筑的顶部,这样可以减缓下降时的冲击力,减少受伤的可能性。
请问,他最多可以经过多少幢不同建筑的顶部(包含初始时的建筑)?
输入格式
输入数据第一行是一个整数K,代表有K组测试数据。
每组测试数据包含两行:第一行是一个整数N,代表有N幢建筑。第二行包含N个不同的整数,每一个对应一幢建筑的高度h,按照建筑的排列顺序给出。
输出格式
对于每一组测试数据,输出一行,包含一个整数,代表怪盗基德最多可以经过的建筑数量。
数据范围
1≤K≤100 1≤N≤100 0<h<10000
输入样例
1 2 3 4 5 6 7
| 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
|
输出样例
题解
怪盗基德可以从左到右滑行,也可以从右到左滑行。基德每次滑行必须严格下降,求最大长度。
相当于计算 Max( 最长上升子序列长度,最长下降子序列长度)。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| #include<bits/stdc++.h> using namespace std; const int maxn = 200;
int t,n,res; int a[maxn],dp[maxn];
void solve(){ res = 0; cin>>n; for(int i = 1 ; i <= n ; i ++){ cin>>a[i]; } memset(dp,0,sizeof(dp)); for(int i = 2 ; i <= n ; i ++){ for(int j = 1 ; j < i ; j ++){ if(a[i] > a[j]){ dp[i] = max(dp[i], dp[j] + 1); res = max(res, dp[i]); } } } memset(dp,0,sizeof(dp)); for(int i = 2 ; i <= n ; i ++){ for(int j = 1 ; j < i ; j ++){ if(a[i] < a[j]){ dp[i] = max(dp[i], dp[j] + 1); res = max(res, dp[i]); } } } cout<<res+1<<"\n"; }
int main(void){ cin>>t; while(t--){ solve(); } return 0; }
|
1014 登山
五一到了,ACM队组织大家去登山观光,队员们发现山上一共有N个景点,并且决定按照顺序来浏览这些景点,即每次所浏览景点的编号都要大于前一个浏览景点的编号。
同时队员们还有另一个登山习惯,就是不连续浏览海拔相同的两个景点,并且一旦开始下山,就不再向上走了。
队员们希望在满足上面条件的同时,尽可能多的浏览景点,你能帮他们找出最多可能浏览的景点数么?
输入格式
第一行包含整数N,表示景点数量。
第二行包含N个整数,表示每个景点的海拔。
输出格式
输出一个整数,表示最多能浏览的景点数。
数据范围
2≤N≤1000
输入样例
1 2
| 8 186 186 150 200 160 130 197 220
|
输出样例
题解
登山队先上山后下山,且海拔必须严格上升和下降。
本质是 求 先上升后下降
的最大序列长度。因此,我们从左到右遍历一次上升序列,在从右到左遍历一次下降序列。
dp1[i] + 1 表示从1到i的最大长度,dp2[i] + 1表示从n到i的最大长度。
res = max(res, dp1[i] + dp2[i] + 1)
此处不是+2,而是+1,是因为要减去重合点 i。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #include<bits/stdc++.h> using namespace std; const int maxn = 2000;
int n,res; int a[maxn],dp1[maxn],dp2[maxn];
int main(void){ cin>>n; for(int i = 1 ; i <= n ; i ++){ cin>>a[i]; } for(int i = 2 ; i <= n ; i ++){ for(int j = 1 ; j < i ; j ++){ if(a[i] > a[j]){ dp1[i] = max(dp1[i],dp1[j]+1); } } } for(int i = n-1 ; i >= 1 ; i --){ for(int j = n ; j >= i+1 ; j --){ if(a[i] > a[j]){ dp2[i] = max(dp2[i], dp2[j]+1); } } } for(int i = 1 ; i <= n ; i ++){ res = max(res,dp1[i]+dp2[i]); } cout<<res+1; return 0; }
|
482 合唱队形
NN 位同学站成一排,音乐老师要请其中的 (N−K)(N−K)
位同学出列,使得剩下的 KK 位同学排成合唱队形。
合唱队形是指这样的一种队形:设 KK 位同学从左到右依次编号为
1,2…,K他们的身高分别为 T1,T2,…,TK 则他们的身高满足
T1<…Ti+1>…>TK(1≤i≤K)
你的任务是,已知所有 N
位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
输入格式
输入的第一行是一个整数 N,表示同学的总数。
第二行有 N 个整数,用空格分隔,第 i个整数 Ti 是第 i
位同学的身高(厘米)。
输出格式
输出包括一行,这一行只包含一个整数,就是最少需要几位同学出列。
数据范围
2≤N≤100 130≤Ti≤230
输入样例
1 2
| 8 186 186 150 200 160 130 197 220
|
输出样例
题解
这题和1014类似,问最少需要多少人出队,其实就是在问最多剩下多少人。
这个问题和1014相同,使用总数减去结果即可。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #include<bits/stdc++.h> using namespace std; const int maxn = 200;
int n,res; int a[maxn],dp1[maxn],dp2[maxn];
int main(void){ cin>>n; for(int i = 1 ; i <= n ; i ++){ cin>>a[i]; } for(int i = 2 ; i <= n ; i ++){ for(int j = 1 ; j < i ; j ++){ if(a[i] > a[j]){ dp1[i] = max(dp1[i],dp1[j]+1); } } } for(int i = n-1 ; i >= 1 ; i --){ for(int j = n ; j >= i+1 ; j --){ if(a[i] > a[j]){ dp2[i] = max(dp2[i], dp2[j]+1); } } } for(int i = 1 ; i <= n ; i ++){ res = max(res,dp1[i]+dp2[i]); } cout<<n-(res+1); return 0; }
|
1012 友好城市
Palmia国有一条横贯东西的大河,河有笔直的南北两岸,岸上各有位置各不相同的N个城市。
北岸的每个城市有且仅有一个友好城市在南岸,而且不同城市的友好城市不相同。
每对友好城市都向政府申请在河上开辟一条直线航道连接两个城市,但是由于河上雾太大,政府决定避免任意两条航道交叉,以避免事故。
编程帮助政府做出一些批准和拒绝申请的决定,使得在保证任意两条航线不相交的情况下,被批准的申请尽量多。
输入格式
第1行,一个整数N,表示城市数。
第2行到第n+1行,每行两个整数,中间用1个空格隔开,分别表示南岸和北岸的一对友好城市的坐标。
输出格式
仅一行,输出一个整数,表示政府所能批准的最多申请数。
数据范围
1≤N≤500 0≤xi≤10000
输入样例
1 2 3 4 5 6 7 8
| 7 22 4 2 6 10 3 15 12 9 8 17 17 4 2
|
输出样例
题解
在河的南北两岸分别有n队友好城市,每对友好城市可以修建桥梁。现在给定每对城市的坐标,求最大桥梁数量。这里只需要用北岸城市坐标排序,并将南岸城市的坐标当作数值,求最长上升子序列即可。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| #include<bits/stdc++.h> using namespace std; const int maxn = 1e4;
int n,res; int dp[maxn];
struct city{ int a,b; bool operator<(const city & rhs){ return a < rhs.a; } }c[maxn];
int main(void){ cin>>n; for(int i = 1 ; i <= n ; i ++){ cin>>c[i].a>>c[i].b; } sort(c+1,c+n+1); for(int i = 2 ; i <= n ; i ++){ for(int j = 1 ; j < i ; j ++){ if(c[i].b > c[j].b){ dp[i] = max(dp[i],dp[j]+1); res = max(dp[i],res); } } } cout<<res+1; return 0; }
|
1016 最大上升子序列和
一个数的序列 bi,当 b1<b2<…<bS
的时候,我们称这个序列是上升的。
对于给定的一个序列(a1,a2,…,aN),我们可以得到一些上升的子序列(ai1,ai2,…,aiK),这里1≤i1<i2<…<iK≤N
比如,对于序列(1,7,3,5,9,4,8),有它的一些上升子序列,如(1,7),(3,4,8)等等。
这些子序列中和最大为18,为子序列(1,3,5,9)的和。
你的任务,就是对于给定的序列,求出最大上升子序列和。
注意,最长的上升子序列的和不一定是最大的,比如序列(100,1,2,3)的最大上升子序列和为100,而最长上升子序列为(1,2,3)。
输入格式
输入的第一行是序列的长度N。
第二行给出序列中的N个整数,这些整数的取值范围都在0到10000(可能重复)。
输出格式
输出一个整数,表示最大上升子序列和。
数据范围
1≤N≤1000
输入样例
输出样例
题解
求最大长度:dp[i] = max(dp[i], dp[j] + 1);
求最大和:dp[i] = max(dp[i], dp[j] + a[i]);
- 注意需要考虑 长度为1的最大值,例如:100 1 2 3
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #include<bits/stdc++.h> using namespace std; const int maxn = 2000;
int n,res; int a[maxn],dp[maxn];
int main(void){ cin>>n; for(int i = 1 ; i <= n ; i ++){ cin>>a[i]; dp[i] = a[i]; res = max(dp[i], res); } for(int i = 2 ; i <= n ; i ++){ for(int j = 1 ; j < i ; j ++){ if(a[i] > a[j]){ dp[i] = max(dp[i], dp[j] + a[i]); res = max(dp[i], res); } } } cout<<res; return 0; }
|
1010 拦截导弹
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。
但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。
某天,雷达捕捉到敌国的导弹来袭。
由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数,导弹数不超过1000),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
输入格式
共一行,输入导弹依次飞来的高度。
输出格式
第一行包含一个整数,表示最多能拦截的导弹数。
第二行包含一个整数,表示要拦截所有导弹最少要配备的系统数。
数据范围
雷达给出的高度数据是不大于 30000 的正整数,导弹数不超过 1000。
输入样例
1
| 389 207 155 300 299 170 158 65
|
输出样例
题解
求解这道题的关键在于下面这个性质 \[
覆盖整个序列的最少的不上升子序列的个数 == 最长上升子序列的长度
\] 所以,这题的第二问可以转化为:求最长上升子序列的长度。
证明:AcWing
1010. 拦截导弹【附贪心证明】 - AcWing
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| #include<bits/stdc++.h> using namespace std; const int maxn = 2000;
int a[maxn],dp1[maxn],dp2[maxn]; int n,res,cnt;
int main(void){ while(cin>>a[n+1]){n++;} for(int i = 2 ; i <= n ; i ++){ for(int j = 1 ; j < i ; j ++){ if(a[i] <= a[j]) dp1[i] = max(dp1[i], dp1[j] + 1); else dp2[i] = max(dp2[i], dp2[j]+1); res = max(dp1[i],res); cnt = max(dp2[i],cnt); } } cout<<res+1<<"\n"<<cnt+1; return 0; }
|
187 导弹防御系统
为了对抗附近恶意国家的威胁,RR 国更新了他们的导弹防御系统。
一套防御系统的导弹拦截高度要么一直 严格单调
上升要么一直 严格单调 下降。
例如,一套系统先后拦截了高度为 3 和高度为 4
的两发导弹,那么接下来该系统就只能拦截高度大于 4的导弹。
给定即将袭来的一系列导弹的高度,请你求出至少需要多少套防御系统,就可以将它们全部击落。
输入格式
输入包含多组测试用例。
对于每个测试用例,第一行包含整数 n,表示来袭导弹数量。
第二行包含 n 个不同的整数,表示每个导弹的高度。
当输入测试用例 n=0 时,表示输入终止,且该用例无需处理。
输出格式
对于每个测试用例,输出一个占据一行的整数,表示所需的防御系统数量。
数据范围
1≤n≤501≤n≤50
输入样例
输出样例
样例解释
对于给出样例,最少需要两套防御系统。
一套击落高度为 3,43,4 的导弹,另一套击落高度为 5,2,15,2,1
的导弹。
题解
这题需要使用dfs求解。
首先,使用up和down数组分别保存当前所有的上升/下降序列的末尾值
然后,从d=1到d=n依次处理每个数,每次处理考虑四种情况:
1)插入已存在的上升序列;
2)创建并插入新的上升序列;
3)插入已存在的下降序列中;
4)创建一个新下降序列;
最后,使用全局的ans剪枝,考虑所有情况后,得到最优解。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| #include<bits/stdc++.h> using namespace std; const int maxn = 100;
int n,ans; int a[maxn],up[maxn],down[maxn];
void dfs(int d, int u, int v){ if(u+v >= ans) return; if(d == n+1){ ans = min(ans, u+v); return; } int f = 0; for(int i = 1 ; i <= u ; i++){ if(a[d] > up[i]){ f = i; int t = up[i]; up[i] = a[d]; dfs(d+1,u,v); up[i] = t; break; } } if(f == 0){ up[u+1] = a[d]; dfs(d+1,u+1,v); } f = 0; for(int i = 1 ; i <= v ; i++){ if(a[d] < down[i]){ f = i; int t = down[i]; down[i] = a[d]; dfs(d+1,u,v); down[i] = t; break; } } if(f == 0){ down[v+1] = a[d]; dfs(d+1,u,v+1); } }
int main(void){ while(cin>>n, n){ for(int i = 1 ; i <= n ; i ++){ cin>>a[i]; } ans = n; dfs(1,0,0); cout<<ans<<"\n"; } return 0; }
|
272 最长公共上升子序列
熊大妈的奶牛在小沐沐的熏陶下开始研究信息题目。
小沐沐先让奶牛研究了最长上升子序列,再让他们研究了最长公共子序列,现在又让他们研究最长公共上升子序列了。
小沐沐说,对于两个数列 A 和
B,如果它们都包含一段位置不一定连续的数,且数值是严格递增的,那么称这一段数是两个数列的公共上升子序列,而所有的公共上升子序列中最长的就是最长公共上升子序列了。
奶牛半懂不懂,小沐沐要你来告诉奶牛什么是最长公共上升子序列。
不过,只要告诉奶牛它的长度就可以了。
数列 AA 和 BB 的长度均不超过 3000。
输入格式
第一行包含一个整数 N,表示数列 A,B 的长度。
第二行包含 N 个整数,表示数列 A。
第三行包含 N 个整数,表示数列 B。
输出格式
输出一个整数,表示最长公共上升子序列的长度。
数据范围
1≤N≤3000
序列中的数字均不超过 2^31−1。
输入样例
输出样例
题解
设dp[i][j]为:考虑从a的[1,i]区间 和
b的[1,j]的区间,且以b[j]结尾的LCIS长度
更新dp[i][j]策略如下:
从小到大遍历每一个i和j,判断a[i]是否等于b[j],
若a[i] != b[j],则a[i]对LCIS的长度没有贡献,dp[i][j] =
dp[i-1][j];
若a[i] == b[j],则往前遍历所有dp[i][k](k < j && a[i] >
b[k]),更新dp[i][j] 为 dp[i][k]+1的最大值;
最后,答案为dp[i][j]的最大值。
代码
O(N^3)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| #include<bits/stdc++.h> using namespace std; const int maxn = 3050;
int n,ans; int a[maxn],b[maxn],dp[maxn][maxn];
int main(void){ 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 ++){ if(a[i] != b[j]) dp[i][j] = dp[i-1][j]; else{ int maxv = 1; for(int k = 1 ; k <= j ; k ++){ if(a[i] > b[k]){ maxv = max(dp[i][k]+1, maxv); } } dp[i][j] = max(dp[i][j], maxv); } ans = max(dp[i][j],ans); } } cout<<ans; return 0; }
|
O(N^2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| #include<bits/stdc++.h> using namespace std; const int maxn = 3050;
int n,ans; int a[maxn],b[maxn],dp[maxn][maxn];
int main(void){ 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 maxv = 1; for(int j = 1 ; j <= n ; j ++){ if(a[i] != b[j]) dp[i][j] = dp[i-1][j]; else dp[i][j] = max(dp[i][j], maxv); if(a[i] > b[j]) maxv = max(maxv, dp[i - 1][j] + 1); ans = max(dp[i][j],ans); } } cout<<ans; return 0; }
|