[loj2265]最长上升子序列

以下内容参考2019年集训队论文《浅谈杨氏矩阵在信息学竞赛中的应用》

1.前置知识

杨表

标准杨表:一张网格图,满足以下条件——

1.设其有$m$行、第$i$行有$a_{i}$个格子(格子左对齐),则$a_{1}ge a_{2}ge ...ge a_{m}$

2.每一个格子内有一个正整数,且每行每列都严格单调递增

3.记$n=sum_{i=1}^{m}a_{i}$(即格子总数),所有格子内的正整数恰构成1到$n$的排列

半标准杨表:一张网格图,满足以下条件——

1.设其有$m$行、第$i$行有$a_{i}$个格子(格子左对齐),则$a_{1}ge a_{2}ge ...ge a_{m}$

2.每一个格子内有一个正整数,且每行单调不下降、每列严格单调递增

(以下杨表默认均指半标准杨表)

记$empty$为空杨表(即$m=0$时),$S_{i,j}$为杨表$S$第$i$行第$j$列的元素

插入和删除

行插入:对于一个杨表$S$,将$xin Z^{+}$行插入$S$,过程如下:

1.找到第$i$行中第一个大于等于$x$的元素,将其与$x$交换(初始$i=1$)

2.将$i$增加1并重复第一步,直至第$i$行中没有大于等于$x$的元素,则将$x$加入到该行末尾

并记最终得到的杨表为$Sleftarrow x$

列插入:与行插入类似,将过程中的"行"将改为"列"即可,并记最终得到的杨表为$x ightarrow S$

删除:对于一个杨表$S$,将第$i$行最后一个元素删除,过程如下:

1.找到第$i-1$行中最后一个小于等于$x$的的元素,将其与$x$交换(初始$x=S_{i,a_{i}}$)

2.将$i$减小1并重复第一步,直至$i=1$则结束

并记最终得到的杨表为$S-i$(其中$1le ile m$)

性质

性质1.1:对杨表执(行/列)插入或删除操作后仍是杨表

性质1.2:行插入和删除操作互为逆运算

关于这一性质,更完整的描述即以下两点——

1.假设在杨表$S$中行插入$x$后最终位于第$i$行,则$S=(Sleftarrow x)-i$

2.假设在杨表$S$中删除第$i$行后结束时的元素为$x$,则$(S-i)leftarrow x=S$

性质1.3:$(Sleftarrow x)^{T}=x ightarrow S^{T}$(其中$S$为标准杨表,$S^{T}$即将其转置)

性质1.4:$(x ightarrow S)leftarrow y=x ightarrow(Sleftarrow y)$

关于这一性质的证明,参考论文的参考文献[10]

2.杨表和排列

对于一个排列${p_{i}}$,构造其对应的杨表$S_{p}=((emptyleftarrow p_{1})leftarrow p_{2})leftarrow...leftarrow p_{n}$

在构造$S_{p}$的过程中,若插入$p_{x}$后$p_{x}$位于第$i$行第$j$列,则令$Q_{p}$第$i$行第$j$列的元素为$x$

性质2.1:$S_{p}$和$Q_{p}$均为标准杨表且两者形状相同(形状即每行元素个数相同)

性质2.2:$S_{{p_{n},p_{n-1},...,p_{1}}}=S_{p}^{T}$

关于这一性质的证明,根据性质1.3和1.4并归纳即可

性质2.3:对于任意两个形状相同的标准杨表$S_{1}$和$S_{2}$,恰存在一个排列$p_{i}$使得$(S_{1},S_{2})=(S_{p},Q_{p})$

关于这一性质的证明,根据性质1.2即可通过$(S_{1},S_{2})$构造排列$p_{i}$

性质2.4:$p_{i}$最长${ m LIS}_{k}$子序列长度为$S_{p}$的前$k$列长度和

(其中${ m LIS}_{k}$序列即最长上升子序列长度小于等于$k$的序列)

关于这一性质的证明,参考论文5.1的部分

3.杨表计数

定义序列${a_{1},a_{2},...,a_{m}}vdash n$当且仅当$a_{1}ge a_{2}ge ...ge a_{m}$且$sum_{i=1}^{m}a_{i}=n$

记$f_{{a_{1},a_{2},...,a_{m}}}$为第$i$行有$a_{i}$个格子的标准杨表个数,显然要有$a_{1}ge a_{2}ge ...ge a_{m}$

性质3.1:$sum_{avdash n}f_{a}^{2}=n!$

性质3.2:$f_{a}=frac{n!}{prod_{1le ile m,1le jle a_{i}}h_{a}(i,j)}$(其中$h_{a}(i,j)$表示$(i,j)$正右和正下方的格子数+1)

关于这一性质的证明,参考论文6.1的部分

类似地,记$g_{n,{a_{1},a_{2},...,a_{m}}}$为第$i$行有$a_{i}$个格子且值域为$[1,n]$的半标准杨表数

性质3.3:$g_{n,a}=prod_{1le ile m,1le jle a_{i}}frac{n+j-i}{h_{a}(i,j)}$

关于这一性质的证明,参考论文8.1和8.2的部分

4.其他

论文中还有以下几部分的内容:

(1)杨表和矩阵(第4部分)

(2)杨表和路径计数(6.2的部分和第7部分)

(3)复杂的杨表计数(第9部分)

(补充一下$q$的范围:$qle 2 imes 10^{5}$)

关于本题,将询问离线后按照$m$从小到大排序,并将所有数依次行插入杨表

暴力插入的复杂度为$o(nlog n)$,显然无法通过

对于杨表$S$,仅维护$S$的前$sqrt{n}$行和$S^{T}$的前$sqrt{n}$行(也即$S$的前$sqrt{n}$列),注意到$S$中每一个格子的左上角格子必然是全的,因此这包含了$S$的所有元素(否则元素个数$>n$)

前者单次插入复杂度即$o(sqrt{n}log n)$,但后者仍难以处理

构造$S'$也为所有数依次行插入杨表的结果,但将比较中的"大于等于"改为"小于",那么$S'$前$k$行的长度和即为最长${ m LIS}_{k}$子序列,注意到这与$S^{T}$相同

换言之,有$S'$和$S^{T}$形状相同,同时我们仅关心于形状而不关心于具体的数,即可以维护$S'$代替$S^{T}$,同样单次插入复杂度为$o(sqrt{n}log n)$

关于查询,根据性质2.4即求前$k$列的长度和,注意到每次插入对杨表的形状只有一处变化,因此用树状数组维护即可,修改和询问复杂度均为$o(log n)$

总复杂度为$o(nsqrt{n}log n+qlog n)$,可以通过

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define N 50005
 4 #define K 250
 5 struct Data{
 6     int l,k,id;
 7     bool operator < (const Data &a)const{
 8         return l<a.l;
 9     }
10 }q[N<<2];
11 vector<int>v0[K],v1[K];
12 int n,m,k,a[N],f[N],ans[N<<2];
13 int lowbit(int k){
14     return (k&(-k));
15 }
16 void update(int k){
17     while (k<=n){
18         f[k]++;
19         k+=lowbit(k);
20     }
21 }
22 int query(int k){
23     int ans=0;
24     while (k){
25         ans+=f[k];
26         k-=lowbit(k);
27     }
28     return ans;
29 }
30 void add0(int x){
31     for(int i=1;i<=k;i++){
32         if ((!v0[i].size())||(v0[i].back()<x)){
33             v0[i].push_back(x);
34             if (v0[i].size()>k)update(v0[i].size());
35             return;
36         }
37         swap(*lower_bound(v0[i].begin(),v0[i].end(),x),x);
38     }
39 }
40 void add1(int x){
41     for(int i=1;i<=k;i++){
42         if ((!v1[i].size())||(v1[i].back()<=x)){
43             v1[i].push_back(x);
44             update(i);
45             return;
46         }
47         swap(*upper_bound(v1[i].begin(),v1[i].end(),x),x);
48     }
49 }
50 int main(){
51     scanf("%d%d",&n,&m);
52     k=(int)sqrt(n);
53     for(int i=1;i<=n;i++)scanf("%d",&a[i]);
54     for(int i=1;i<=m;i++){
55         scanf("%d%d",&q[i].l,&q[i].k);
56         q[i].id=i;
57     }
58     sort(q+1,q+m+1);
59     for(int i=1,j=1;i<=n;i++){
60         add0(a[i]),add1(-a[i]);
61         while ((j<=m)&&(q[j].l==i)){
62             ans[q[j].id]=query(q[j].k);
63             j++;
64         }
65     }
66     for(int i=1;i<=m;i++)printf("%d
",ans[i]);
67 }
View Code
原文地址:https://www.cnblogs.com/PYWBKTDA/p/15220156.html