IS(上升子序列)

前言:

  这是一篇杂题选讲+作者口胡的博客,不喜勿喷。

正文:

  提示:在阅读时请留意加粗的字体是“极长”还是“最长”。
  今天改题时碰到了一道关于线段树维护单调栈求极大上升子序列的题,没看出来,懵了一整个下午,感到对这个知识点不熟,所以来总结一下。

What is IS??

  其实我是将LIS删了一个L造的这个词,。所谓上升子序列,就是在原序列中抽出一个子序列(不一定连续),他满足任意\(i<j\)\(v[i]<v[j]\)
  最长上升子序列(\(LIS\)):就是\(IS\)里最长的。
  极大上升子序列:就是一个不会被其他任意一个\(IS\)包含的\(IS\)
  这两个是常考点,然而我总是看不出来。

他们的性质:

  主要是考虑极长上升子序列,因为最大上升子序列的性质除了最大外就和他的性质一样了。

1.满足任意\(i<j\)\(v[i]<v[j]\)
2.在原序列中,极长上升子序列\({p_{1},p_{2},p_{3}......p_{m}}(p_{1}<p_{2}<p_{3}<......<p_{m})\)满足:
不存在一个\(j\),使得\(j<p_{1}\)\(v[j]<v[p_{1}]\)
不存在一个\(j\),使得\(j>p_{m}\)\(v[j]>v[p_{m}]\)
不存在一个\(j\),使得\(p_{i}<j<p_{i+1}\)\(v[p_{i}]<v[j]<v[p_{i+1}]\)

  第二条尤为重要,考试时就栽在他上面了。。。。。。

如何求??

  对于最长上升子序列来说:
  传统做法是线性DP。

int c[N];
for(int i=1;i<=n;i++)
{
	c[i]=1;
	for(int j=1;j<i;j++)
		if(v[j]<v[i])
			c[i]=max(c[i],c[j]+1);
}

  复杂度:\(O(N^{2})\)
  有一种线段树优化,叫线段树维护单调栈,可以优化至\(O(Nlog^{2}_{2}N)\)

	int c[N];
	#define lc id<<1
	#define rc id<<1|1
	class Line_tree
	{
		private:
			int maxn[N<<2];
		public:
			void insert(int,int,int,int,int);
			int query(int,int,int,int,int);
	}t;
	void Line_tree::insert(int id,int l,int r,int pos,int val)
	{
		if(l==r){maxn[id]=val;return;}
		int mid=(l+r)>>1;
		if(pos<=mid) insert(lc,l,mid,pos,val);
		else insert(rc,mid+1,r,pos,val);
		maxn[id]=max(maxn[lc],maxn[rc]);
	}
	int Line_tree::query(int id,int l,int r,int st,int en)
	{
		if(st<=l&&r<=en) return maxn[id];
		int mid=(l+r)>>1;
		int ret=-inf;
		if(st<=mid) ret=max(ret,query(lc,l,mid,st,en));
		if(mid<en) ret=max(ret,query(rc,mid+1,r,st,en));
		return ret;
	}
	#undef lc
	#undef rc
	//在主函数里:
	for(int i=1;i<=n;i++)
	{
		int id=0;
		if(v[i]-1>=1)
			id=t.query(1,1,n,1,v[i]-1);
		while(id)
		{
			c[i]=max(c[i],c[id]+1);
			if(v[id]+1>v[i]-1)
				 break;
			id=t.query(1,1,n,v[id]+1,v[i]-1);
		}
		ans=max(ans,c[i]);
	}

  其实它主要的用途是做一些与极大上升子序列有关的统计时用的,比如方案数统计、最小权极大上升子序列等等。。。并不推荐用来单纯求序列,因为求序列的话他的复杂度不如下者。
  还有一种优化,是\(O(Nlog_{2}N)\)的,他就是主要用来求最大序列及方案数了。

#include<bits/stdc++.h>
using namespace std;
namespace STD
{
	#define rr register 
	#define inf INT_MAX
	typedef long long ll;
	const int N=100004;
	const int mod=123456789;
	const int R=100003;
	int n,type;
	int r[N];	
	#define lc id<<1
	#define rc id<<1|1
	struct node
	{
		int maxn;
		ll cnt;
		node(){maxn=cnt=0;}
		node(int maxn_,ll cnt_){maxn=maxn_,cnt=cnt_;}
	};
	class Line_tree
	{
		private:
			node a[R<<2];
			void Insert(int,int,int,int,node);
			node Query(int,int,int,int,int);
		public:
			Line_tree(){}
			void insert(int pos,node val){Insert(1,1,R-1,pos,val);}
			node query(int st,int en)
			{
				if(st>en) return node(0,0);
				return Query(1,1,R-1,st,en);
			}
	}t;
	void Line_tree::Insert(int id,int l,int r,int pos,node val)
	{
		if(l==r)
		{
			if(a[id].maxn==val.maxn)
				a[id].cnt=(a[id].cnt+val.cnt)%mod;
			else if(a[id].maxn<val.maxn)
				a[id]=val;
			return;
		}
		int mid=(l+r)>>1;
		if(pos<=mid) Insert(lc,l,mid,pos,val);
		else Insert(rc,mid+1,r,pos,val);
		a[id]=(a[lc].maxn>a[rc].maxn)?a[lc]:a[rc];
		if(a[lc].maxn==a[rc].maxn)
			a[id].cnt=(a[lc].cnt+a[rc].cnt)%mod;
	}
	node Line_tree::Query(int id,int l,int r,int st,int en)
	{
		if(st<=l&&r<=en) return a[id];
		int mid=(l+r)>>1;
		node ret=node(-inf,0),temp;
		if(st<=mid)
		{
			temp=Query(lc,l,mid,st,en);
			if(temp.maxn>ret.maxn)
				ret=temp;
			else if(temp.maxn==ret.maxn)
				ret.cnt=(ret.cnt+temp.cnt)%mod;
		}
		if(mid<en)
		{
			temp=Query(rc,mid+1,r,st,en);
			if(temp.maxn>ret.maxn)
				ret=temp;
			else if(temp.maxn==ret.maxn)
				ret.cnt=(ret.cnt+temp.cnt)%mod;
		}
		return ret;
	}
	#undef lc
	#undef rc
	int read()
	{
		rr int x_read=0,y_read=1;
		rr char c_read=getchar();
		while(c_read<'0'||c_read>'9')
		{
			if(c_read=='-') y_read=-1;
			c_read=getchar();
		}
		while(c_read<='9'&&c_read>='0')
		{
			x_read=(x_read<<3)+(x_read<<1)+(c_read^48);
			c_read=getchar();
		}
		return x_read*y_read;
	}
};
using namespace STD;
int main()
{
	n=read(),type=read();
	for(rr int i=1;i<=n;i++)
		r[i]=read();
	int ans=-inf;
	ll num=0;
	for(rr int i=1;i<=n;i++)
	{
		node temp=t.query(1,r[i]-1);
		temp.maxn++;
		if(temp.cnt==0)
			temp.cnt=1;
		if(ans<temp.maxn)
		{
			num=temp.cnt;
			ans=temp.maxn;
		}
		else if(ans==temp.maxn)
			num=(num+temp.cnt)%mod;
		t.insert(r[i],temp);
	}
	cout<<ans;
	if(type==1)
		cout<<num;
}

  其实这两种方法主要的区别是:
  前者通过那个枚举id的循环枚举i之前已有的极长序列,以此为算法框架,来进行一些额外的维护,他可以知道具体的方案,即对应下标,有了下标,就可以以此为框架维护线段树之外的东西了。
  后者线段树维护的是长度,因此只能局限于对长度的求解,稍带着维护方案数,他无法知道具体方案即下标,局限性相对大。
  另外,在统计极长子序列的方案总数时,可以直接从后往前跳单调不降序列然后累加结果即可,就像这样:

//在这之前你已经求好了方案数
int x=-inf;//随便一个极小值
for(int i=n;i>=1;i--)
{
	if(v[i]>=x)
	{
		x=v[i];
		ans+=c[i];
	}
}

2021-07-29 21:30:08 星期四  现役

posted @ 2021-07-29 21:32  Geek_kay  阅读(274)  评论(2编辑  收藏  举报