死嗑 最長上升子序列(LIS)

CourserLi發表於2020-11-04

【模板】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(1aK) 組導彈中任取一個導彈,必定可以從 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(1iK) 滿足左邊和右邊都是遞減,那麼左邊的每個元素的最長上升序列就是它的下標(從 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+ans21(去除重複),因為想出列最少,那麼就想要留下的最多,從中最小的 n − ( a n s 1 + a n s 2 − 1 ) n-(ans1+ans2-1) n(ans1+ans21) 即答案

注意
逆序 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);

相關文章