bzoj4904 [Ctsc2017]最长上升子序列

时间:2023-03-08 16:51:55

我们发现他让求的东西很奇怪,于是通过某D开头定理,我们转化为前m位的序列用k个不上升子序列最多能覆盖多少。数据范围小的时候可以网络流做,但是这道题显然不支持网络流的复杂度。然后有一个奇怪的东西叫杨氏矩阵,详见acdreamers    acdreamers。但是我们又不能每次O(k)来扫,之后又有一个神奇的性质就是对于反串建的这个矩阵的形状和原矩阵转置的形状是一样的,于是我们可以在原矩阵中找到前$\sqrt{n}$个,之后的长度肯定小于$\sqrt{n}$于是我们就可以在转置后的矩阵中计算了。

 #include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
#define N 50505
#define M 233
using namespace std;
struct data{int pos,K,id,ans;}d[N<<];
bool cmp1(data a,data b){return a.pos<b.pos;}
bool cmp2(data a,data b){return a.id<b.id;}
int n,m,a[N];
namespace A{
int a[M+][N],len[M+];
void insert(int x,int y,int v){
if(x>M)return ;
y=min(y,len[x]);
while(y&&a[x][y]<v)y--;y++;
if(y>len[x]){
a[x][++len[x]]=v;
return;
}
else{
insert(x+,y,a[x][y]);
a[x][y]=v;
}
}
};
namespace B{
int a[M+][N],len[M+];
void insert(int x,int y,int v){
if(x>M)return ;
y=min(y,len[x]);
while(y&&a[x][y]>=v)y--;y++;
if(y>len[x]){
a[x][++len[x]]=v;
return;
}
else{
insert(x+,y,a[x][y]);
a[x][y]=v;
}
}
};
int query(int x){
int ans=;
for(int i=;i<=x&&i<=M;i++)
ans+=A::len[i];
if(x>M){
for(int i=;i<=M&&B::len[i]>M;i++)
ans+=min(B::len[i],x)-M;
}
return ans;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=;i<=n;i++)scanf("%d",&a[i]);
for(int i=;i<=m;i++)
scanf("%d%d",&d[i].pos,&d[i].K),d[i].id=i;
sort(d+,d+m+,cmp1);
for(int i=,j=;i<=n;i++){
A::insert(,N,a[i]);
B::insert(,N,a[i]);
for(;j<=m&&d[j].pos==i;j++)
d[j].ans=query(d[j].K);
}
sort(d+,d+m+,cmp2);
for(int i=;i<=m;i++)
printf("%d\n",d[i].ans);
return ;
}