给定一个 的排列 , 次询问,每次查询 的 LIS 长度。
,。
考虑求序列 的 LIS 长度的经典贪心算法:
维护一个集合 ,从前往后遍历 的每个元素 :
- 找到最小的 ;
- 若找到了则从 中删掉 ,找不到就不管;
- 将 加入 ;
最终 即为 LIS 长度。
原理是最长反链等于最小链覆盖。
设 对表示 执行上面的过程最终得到的 。
观察发现,,并且 和 最多相差 ,即最多会新插入一个数。
证明考虑在前面插入一个数后原来 中的数肯定不会消失(消失只能是因为被后面的数顶掉),而最多在链覆盖中增加一条链( 中最多插入一个数)。
那么考虑扫描线, 从左到右扫描的同时维护每个数 在 的多少个 中出现了,记为 。注意到 肯定是出现在 这个前缀的集合中,那么区间 答案就是 扫到 时 的 的个数。
考虑 对 的影响,相当于从 开始在值域上往后遍历,将前缀最大值的位置 找出来,然后将 ,然后令 。
这其实是 AT_joisc2016_h 回転寿司 的 版本,最后多一步将 改为 。而求答案可以用树状数组维护每个 在 数组中出现过 次,那么单步扫描线相当于将 加一,然后将结束后的 的 减一。
做法考虑分块,假设块长为 。
对于整块,一个数进入一个块后出来的数显然只和这个块的最大值有关系,那么每个块开个大根堆,每次若 比堆顶小则交换一下 和堆顶。
对于散块,简单分讨可以发现对于两次对整块的修改 ,先做 再做 和先做 再做 的结果是一样的,所以不妨钦定是从小到大操作。具体的,把对整块的操作存进一个小根堆,然后从左往右遍历块内的 ,若 比堆顶大,那么就交换 和堆顶。这样就可以在 的复杂度重构某个块,从而暴力进行散块的修改。
那么总复杂度为 ,所以 应取 。不过由于修改整块常数较小(只用进行至多一次交换堆顶操作),所以 可以开小一点。
回到本题,再用树状数组维护 即可做到 ,也可以用修改 查询 的前缀和分块做到 。
代码如下:
#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
template<typename T>
inline void read(T &x)
{
	x=0;
	T w=1;
	char c=getchar();
	while(c<'0'||c>'9') w=(c=='-'?-w:w),c=getchar();
	while(c>='0'&&c<='9') x=x*10+(c^48),c=getchar();
	x*=w;
}
const int S=100005,BS=150,QS=1000005;
int n,q;
int a[S],c[S];
int lb[S/BS+5],rb[S/BS+5];
priority_queue<int> b[S/BS+5],que[S/BS+5];
vector<pair<int,int> > vec[S];
int tr[S],ans[QS];
inline void dwntag(int u)
{
	if(que[u].empty()) return;
	int l=lb[u],r=rb[u];
	for(int i=l;i<=r;i++)
	{
		int x=-que[u].top();
		if(c[i]>x)
		{
			swap(c[i],x);
			que[u].pop();
			que[u].push(-x);
		}
	}
	while(!que[u].empty()) que[u].pop();
}
inline void rebuild(int u)
{
	int l=lb[u],r=rb[u];
	while(!b[u].empty()) b[u].pop();
	for(int i=l;i<=r;i++) b[u].push(c[i]);
}
inline int doit(int l,int x)
{
	int ul=(l-1)/BS+1;
	if(rb[ul]==n)
	{
		dwntag(ul);
		for(int i=l;i<=n;i++) if(c[i]>x) swap(c[i],x);
		rebuild(ul);
		return x;
	}
	dwntag(ul);
	for(int i=l;i<=rb[ul];i++) if(c[i]>x) swap(c[i],x);
	rebuild(ul);
	for(int i=ul+1;i<=(n-1)/BS+1;i++)
	{
		int mx=b[i].top();
		if(mx>x)
		{
			que[i].push(-x);
			swap(mx,x);
			b[i].pop();
			b[i].push(mx);
		}
	}
	return x;
}
inline void addtr(int p,int x){for(int i=p;i<=n;i+=i&-i) tr[i]+=x;}
inline int quetr(int p)
{
	int res=0;
	for(int i=p;i>=1;i-=i&-i) res+=tr[i];
	return res;
}
int main()
{
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	read(n),read(q);
	for(int i=1;i<=n;i++) read(a[i]);
	for(int i=1;i<=n;i+=BS)
	{
		int u=(i-1)/BS+1;
		lb[u]=(u-1)*BS+1,rb[u]=min(u*BS,n);
		rebuild(u);
	}
	for(int i=1;i<=q;i++)
	{
		int l,r;
		read(l),read(r);
		vec[r].emplace_back(l,i);
	}
	for(int i=1;i<=n;i++)
	{
		int del=doit(a[i],0);
		int u=(a[i]-1)/BS+1;
		dwntag(u);
		c[a[i]]=i;
		rebuild(u);
		if(del!=0) addtr(n-del+1,-1);
		addtr(n-i+1,1);
		for(auto t:vec[i]) ans[t.second]=quetr(n-t.first+1);
	}
	for(int i=1;i<=q;i++) cout<<ans[i]<<'\n';
	return 0;
}
