死嗑 最長上升子序列(LIS)
【模板】LIS
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N],f[N]; // f[i]:上升子序列長度為 i 的最小末尾數值
int main()
{
int n;cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
int len=1;f[1]=a[1];
for(int i=2;i<=n;i++)
{
if(f[len]<a[i]) f[++len]=a[i];
else
{
// 二分查詢(手寫)
int l=1,r=len,mid;
while(l<r)
{
mid=(l+r)/2;
if(f[mid]>a[i])r=mid;
else l=mid+1;
}
f[l]=a[i];
/* 二分查詢(函式)
int tmp=lower_bound(f+1,f+1+len,a[i])-f;
f[tmp]=a[i];
*/
}
//for(int j=1;j<=i;j++) cout<<f[j]<<' '; //列印
//cout<<" len:"<<len<<endl; //列印長度
}
cout<<len<<endl;
return 0;
}
思路
我們用DP思想來維護一個棧 f
,如果 a[i] > a[i-1]
,則將 a[i]
放入棧 f
中。否則,二分查詢棧 f
中大於 a[i]
的最小的數,並將它替換。長度為
l
e
n
len
len 的棧 f
是目前序列下的最佳
L
I
S
LIS
LIS(並非唯一)
下面是每層迴圈(i++
)後的棧 f
裡的各值(此處的
I
N
F
INF
INF 只是為了湊齊
i
i
i 個數字)
a[7] = 1 3 2 7 4 5 6
i = 1 1 len: 1
i = 2 1 3 len: 2
i = 3 1 2 INF len: 2
i = 4 1 2 7 INF len: 3
i = 5 1 2 4 INF INF len: 3
i = 6 1 2 4 5 INF INF len: 4
i = 7 1 2 4 5 6 INF INF len: 5
//如果當前 f 陣列不是最佳 LIS,甚至 f[1] 的值 1 都可以換掉(為了替換成最佳)
替換
二分查詢的手寫版可以替換為函式版,不懂 lower_bound 函式的點這裡
//舉例
f[4] = 0 1 3 2
lower_bound(f+1,f+2,2)-f; //值 1 和 3 哪個大於等於 2 就返回它的下標(下標是從 1 開始計數)
output:2
導彈攔截
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N],f1[N],f2[N],n=0;
int main()
{
while(cin>>a[++n]); n--;
//最長不上升序列
int len1=1;f1[1]=a[1];
for(int i=2;i<=n;i++)
{
if(f1[len1]>=a[i]) f1[++len1]=a[i];
else {
int tmp=upper_bound(f1+1,f1+1+len1,a[i],greater<int>())-f1;
f1[tmp]=a[i];
}
}
cout<<len1<<endl;
//最長上升序列(LIS)
int len2=1;f2[1]=a[1];
for(int i=2;i<=n;i++)
{
if(f2[len2]<a[i]) f2[++len2]=a[i];
else {
int tmp=lower_bound(f2+1,f2+1+len2,a[i])-f2;
f2[tmp]=a[i];
}
}
cout<<len2<<endl;
return 0;
}
思路
第一問 顯然是求最長不上升序列
第二問 是求最長上升序列(LIS),兩種思路如下:
1、 打個比方,突然有一個導彈的高度大於你當前的攔截最大高度,你肯定攔截不了,所以你肯定需要再來一個系統才能攔截下來。所以只需求最長上升子序列的長度即是需要的系統數量
2、
① 假設打導彈的方法是這樣的:取任意一個導彈,從這個導彈開始將能打的導彈全部打完,而這些導彈全部記為為同一組,再在沒打下來的導彈中任選一個重複上述步驟,直到打完所有導彈
② 假設我們得到了最小劃分的
K
K
K 組導彈,從第
a
(
1
≤
a
≤
K
)
a(1≤a≤K)
a(1≤a≤K) 組導彈中任取一個導彈,必定可以從
a
+
1
a+1
a+1 組中找到一個導彈的高度比這個導彈高(因為假如找不到,那麼它就是比
a
+
1
a+1
a+1 組中任意一個導更高,在打第
a
a
a 組時應該會把
a
+
1
a+1
a+1 組所有導彈一起打下而不是另歸為第
a
+
1
a+1
a+1 組),同樣從
a
+
1
a+1
a+1 組到
a
+
2
a+2
a+2 組也是如此,那麼就可以從前往後在每一組導彈中找一個更高的連起來,連成一條上升子序列,其長度即為
K
K
K
③ 設最長上升子序列長度為
P
P
P,則有
K
<
=
P
K<=P
K<=P,又因為最長上升子序列中任意兩個不在同一組內(否則不滿足單調不升),則有
P
>
=
K
P>=K
P>=K,所以
K
=
P
K=P
K=P
最長 上升/不下降/下降/不上升序列 模板
const int N=1e5+10;
int a[N],f1[N],f2[N],f3[N],f4[N];
//最長上升序列(LIS)
int len1=1;f1[1]=a[1];
for(int i=2;i<=n;i++)
{
if(f1[len1]<a[i]) f1[++len1]=a[i];
else {
int tmp=lower_bound(f1+1,f1+1+len1,a[i])-f1;
f1[tmp]=a[i];
}
}
cout<<len1<<endl;
//最長不下降序列
int len2=1;f2[1]=a[1];
for(int i=2;i<=n;i++)
{
if(f2[len2]<=a[i]) f2[++len2]=a[i];
else {
int tmp=upper_bound(f2+1,f2+1+len2,a[i])-f2;
f2[tmp]=a[i];
}
}
cout<<len2<<endl;
//最長下降序列
int len3=1;f3[1]=a[1];
for(int i=2;i<=n;i++)
{
if(f3[len3]>a[i]) f3[++len3]=a[i];
else {
int tmp=lower_bound(f3+1,f3+1+len3,a[i],greater<int>())-f3;
f3[tmp]=a[i];
}
}
cout<<len3<<endl;
//最長不上升序列
int len4=1;f4[1]=a[1];
for(int i=2;i<=n;i++)
{
if(f4[len4]>=a[i]) f4[++len4]=a[i];
else {
int tmp=upper_bound(f4+1,f4+1+len4,a[i],greater<int>())-f4;
f4[tmp]=a[i];
}
}
cout<<len4<<endl;
合唱隊形
#include <bits/stdc++.h>
using namespace std;
const int N=1e3+10;
int a[N],f1[N],f2[N],ans1[N],ans2[N];
int main()
{
int n;cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
int len1=1;f1[1]=a[1];ans1[1]=1; //順序 LIS
for(int i=2;i<=n;i++)
{
if(f1[len1]<a[i]) f1[++len1]=a[i];
else {
int tmp=lower_bound(f1+1,f1+1+len1,a[i])-f1;
f1[tmp]=a[i];
}
ans1[i]=len1;
}
int len2=1;f2[1]=a[n];ans2[n]=1; //逆序 LIS
for(int i=n-1;i>=1;i--)
{
if(f2[len2]<a[i]) f2[++len2]=a[i];
else {
int tmp=lower_bound(f2+1,f2+1+len2,a[i])-f2;
f2[tmp]=a[i];
}
ans2[i]=len2;
}
//for(int i=1;i<=n;i++) cout<<ans1[i]<<' '<<ans2[i]<<endl;
int minn=INT_MAX;
for(int i=1;i<=n;i++) minn=min(minn,n-(ans1[i]+ans2[i]-1));
cout<<minn<<endl;
return 0;
}
思路
我們想一想,要想使
T
i
(
1
≤
i
≤
K
)
T_i(1≤i≤K)
Ti(1≤i≤K) 滿足左邊和右邊都是遞減,那麼左邊的每個元素的最長上升序列就是它的下標(從
1
1
1 開始),右邊同理,左右兩邊元素一直到
T
i
T_i
Ti 的最長上升序列之和減一(去除重複)就是
K
K
K(總人數);舉個例子,要是左邊的某個元素不滿足最長上升序列,那麼左邊一直到
T
i
T_i
Ti 的和就少了
1
1
1,右邊不變,最後總和也相比
K
K
K 少了
1
1
1,若是把這個人給去掉,才滿足題意,所以最後答案就是
1
1
1
我們先看從 T 1 T_1 T1 到 T i T_i Ti 這一段單調遞增的序列,再看 T i T_i Ti 到 T K T_K TK 這一段單調遞減的序列,那麼問題就解決了,先從 1 1 1 到 n n n 求一趟最長上升序列的 l e n len len 並用陣列 a n s 1 ans1 ans1 儲存,然後從 n n n 到 1 1 1 也求一趟並用陣列 a n s 2 ans2 ans2 儲存,最後列舉全部的 a n s 1 + a n s 2 − 1 ans1+ans2-1 ans1+ans2−1(去除重複),因為想出列最少,那麼就想要留下的最多,從中最小的 n − ( a n s 1 + a n s 2 − 1 ) n-(ans1+ans2-1) n−(ans1+ans2−1) 即答案
注意
逆序
L
I
S
≠
LIS\neq
LIS= 最長下降序列,因為它們每個元素
l
e
n
len
len 的不同
Super Jumping! Jumping! Jumping!
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N],f[N],ans[N],maxn;
//f[i] 以目前元素為末尾的 LIS長度,並非整個序列的 LIS長度
//ans[i] 以目前元素為末尾的上升序列的最大遞增欄位和,並非整個序列的最大遞增欄位和
int main()
{
int n;
while(~scanf("%d",&n),n>0)
{
memset(f,0,sizeof(f));
memset(ans,0,sizeof(ans));
maxn=0;
for(int i=1;i<=n;i++) cin>>a[i];
f[1]=1;ans[1]=a[1];
for(int i=2;i<=n;i++) {
for(int j=1;j<i;j++) {
if(a[i]>a[j]) {
f[i]=max(f[i],f[j]+1);
ans[i]=max(ans[i],ans[j]+a[i]);
}
}
}
for(int i=1;i<=n;i++) {
//cout<<f[i]<<' ';
maxn=max(maxn,ans[i]);
}
cout<<maxn<<endl;
}
return 0;
}
思路
n
2
n^2
n2的做題思路。注意 ans[i]
並不是所有序列中的最大遞增欄位和,f[i]
同理不是所有序列中的最大
L
I
S
LIS
LIS 長度【本題並沒有要求算 f[i]
,我只是單純的列個模板】
FatMouse’s Speed
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int f1[N],f2[N],ans1[N],ans2[N];
struct Node {
int weight,speed,num;
}node[N];
bool cmp1(Node &a,Node &b)
{
if(a.weight!=b.weight) return a.weight<b.weight;
else return a.speed<b.speed;
}
bool cmp2(Node &a,Node &b)
{
if(a.speed!=b.speed) return a.speed<b.speed;
else return a.weight>b.weight;
}
int main()
{
int a,b,n=1;
while(~scanf("%d %d",&a,&b)!=EOF)
{
node[n].weight=a;
node[n].speed=b;
node[n].num=n; //不能寫成 n++
n++;
}
sort(node+1,node+n,cmp1);
f1[1]=node[1].speed;ans1[1]=node[1].num;int len1=1;
for(int i=2;i<n;i++)
{
if(f1[len1]>=node[i].speed) {
f1[++len1]=node[i].speed;
ans1[len1]=node[i].num;
}
else
{
int tmp=upper_bound(f1+1,f1+1+len1,node[i].speed,greater<int>())-f1;
f1[tmp]=node[i].speed;
ans1[tmp]=node[i].num;
}
}
/*
for(int i=1;i<n;i++)
{
cout<<node[i].weight<<' '<<node[i].speed<<' '<<node[i].num<<endl;
}
cout<<endl;
*/
sort(node+1,node+n,cmp2);
f2[1]=node[1].weight;ans2[1]=node[1].num;int len2=1;
for(int i=2;i<n;i++)
{
if(f2[len2]<=node[i].weight) {
f2[++len2]=node[i].weight;
ans2[len2]=node[i].num;
}
else
{
int tmp=upper_bound(f2+1,f2+1+len2,node[i].weight)-f2;
f2[tmp]=node[i].weight;
ans2[tmp]=node[i].num;
}
}
/*
for(int i=1;i<n;i++)
{
cout<<node[i].weight<<' '<<node[i].speed<<' '<<node[i].num<<endl;
}
*/
cout<<((len1>len2)?len1:len2)<<endl;
if(len1>len2)
for(int i=1;i<=len1;i++)
cout<<ans1[i]<<endl;
else
for(int i=1;i<=len2;i++)
cout<<ans2[i]<<endl;
return 0;
}
思路
這道簡單題花了我一個下午你信不信?
整體思路非常簡單,先將結構體先按照體重遞增排序,再對排好的序列的速度進行求最長不上升序列,得到 len1
;再將結構體先按照速度遞減排序,再對排好的序列的體重進行求最長不下降序列,得到 len2
;最後比較兩者哪個大就取哪個的 ans[i]
(最長不上升/不下降序列)作為結果
但是!其中包含許多細節,比如賦值時的 =n; n++
不能直接寫成 =n++
(我也不知道為什麼) ,又比如 sort 括號內的 +n
而不是 +n-1
(因為是從下標1開始賦值的),再比如最開始 while(~scanf(...)!=EOF)
的運用。總而言之,這道題算是考察到了很多知識點,也考察了細心程度,有機會的話,可以再做一次加深印象 (相信你做完之後對
L
I
S
LIS
LIS 的理解程度可以更上一層)
回顧(結構體的 sort 用法)
下面兩種方法經排序得出的結果都是【體重嚴格遞增,若體重相同則速度嚴格遞減】
//結構體內含 cmp函式
struct Node {
int weight,speed;
bool operator < (const Node &b)const
{
if(weight!=b.weight) return weight<b.weight;
else return speed<b.speed;
}
}node[N];
sort(node,node+n);
//結構體外含 cmp函式
struct Node {
int weight,speed;
}node[N];
bool cmp(Node &a,Node &b)
{
if(a.weight!=b.weight) return a.weight<b.weight;
else return a.speed<b.speed;
}
sort(node,node+n,cmp);
相關文章
- 最長上升子序列LIS 詳解+變形+擴充
- 最長上升子序列
- 【部分轉載】:【lower_bound、upperbound講解、二分查詢、最長上升子序列(LIS)、最長下降子序列模版】
- NlogN 求最長不下降子序列(LIS)
- DP筆記最長上升子序列(LIS)以及零件分組問題筆記
- 線性dp:最長上升子序列
- 動態規劃:最長上升子序列動態規劃
- 最長上升子序列動態規劃動態規劃
- 動態規劃-最長上升子序列模型動態規劃模型
- 線性dp--最長上升子序列變形
- 最長上升子串
- [線性dp] 合唱隊形(最長上升子序列模型)模型
- 動態規劃求解最長上升子序列問題動態規劃
- Leetcode 300 最長遞增子序列(LIS板題)LeetCode
- LeetCode 1626. 無矛盾的最佳球隊---【動態規劃】最長上升子序列變換版-->最大上升子序列和LeetCode動態規劃
- 最長公共子序列
- bzoj3173: [Tjoi2013]最長上升子序列(樹狀陣列)陣列
- 【LeetCode動態規劃#14】子序列系列題(最長遞增子序列、最長連續遞增序列、最長重複子陣列、最長公共子序列)LeetCode動態規劃陣列
- 最長公共子序列(JAVA)Java
- 北京大學郭煒-最長上升子序列 動態規劃講解動態規劃
- LeetCode 300. 最長上升子序列(Python、動態規劃、貪心演算法)LeetCodePython動態規劃演算法
- 線性dp:最長公共子序列
- 最長公共子序列求方案數
- 594. 最長和諧子序列
- java 實現 最長公共子序列Java
- 最長公共子序列 Longest Common Subsequence
- LeetCode 1143.最長公共子序列LeetCode
- 力扣1143. 最長公共子序列 動態規劃之最長公共子序列力扣動態規劃
- LeetCode516. 最長迴文子序列LeetCode
- 動態規劃-最長公共子序列動態規劃
- 動態規劃——最長公共子序列動態規劃
- 最長公共子序列你學會了嗎
- 動態規劃(最長公共子序列LCS)動態規劃
- 最長公共子序列LCS 輸出所有LCS
- 51Nod 1006 最長公共子序列Lcs
- P1637 三元上升子序列
- lgP1637 三元上升子序列
- Day 45 | 300.最長遞增子序列 、674. 最長連續遞增序列 、718. 最長重複子陣列陣列