部分参考凄魉博客。
杨氏矩阵(杨表)是这样的二维数组,满足 a i , j ≤ a i + 1 , j , a i , j ≤ a i , j + 1 a_{i,j}\leq a_{i+1,j},a_{i,j}\leq a_{i,j+1} ai,j≤ai+1,j,ai,j≤ai,j+1。以 a 1 , 1 a_{1,1} a1,1 为根的话,杨表满足堆的性质。以 a 1 , m a_{1,m} a1,m 为根,满足平衡树的性质。下图就是一个杨表:
可以发现,杨表不一定是一个矩形。我们接下来解决两个问题:
第一个问题,给定一个阶梯型矩阵,共有 n 个位置,求把 1~n 填进去构成杨氏矩阵的数量。
根据钩子定理,答案等于 n ! ∏ h i , j \frac {n!} {\prod h_{i,j}} ∏hi,jn!, h i , j h_{i,j} hi,j 定义为 (i,j) 这个格子下面的格子数+右边的格子数+1。下图格子里的数标注的就是每个格子的 h 值:
我们感性理解一下这个结论:把这 n 个数随机填入矩阵,每个格子是它下面以及右边格子的最小值的概率是 1 h i , j \frac 1 {h_{i,j}} hi,j1,那么把每个格子的概率乘起来大概就能得到我们的钩子定理了。虽然结论正确,我们的证明确实有疏漏的,因为这些事件不是相互独立的。
我们来解决第二个问题:n 个元素的杨表一共有多少种?先说结论:设 f n f_n fn 表示 n 个元素杨表的数量,那么有 f n = f n − 1 + ( n − 1 ) f n − 2 f_n=f_{n-1}+(n-1)f_{n-2} fn=fn−1+(n−1)fn−2。这个递推式的意义不好解释,但是对于另一个问题,这个递推式就显而易见了,这个问题就是 n 个点任意匹配的方案数。这似乎说明了,这两个问题似乎存在一种对应关系?
我们来证明这个递推式。首先,如果我们有一个大小为 n-1 的杨表,我们一定可以把 n 放到最后一行得到大小为 n 的杨表。这就得到了递推式的前一项。接下来,如果我们有一个 1~n-1 的数 j 和一个 n-1 的杨表,我们同样能唯一确定一个大小为 n 的杨表,方法是这样的:
容易发现操作是可逆的,也就是说 (j,n-1 的杨表) 这个二元组和 n 的杨表是一一对应的。我们的递推式得证。
下面不加证明地给出几个结论:
当然不会证明…
根据杨表的性质,要查询某个元素 x,类似平衡树,一开始位置在 (1,m),如果当前位置元素大于 x,那么这一列都不会合法,删去(等价于向左走一步)。反之同理。这样查询一个数的复杂度是 O ( n + m ) O(n+m) O(n+m)。
我们把一个元素 val 插入到杨表中,需要调用 Insert(1,inf,val)
。类似查询一个数的方式即可。由于杨表第 i 行元素个数大于等于第 i+1 行元素个数。因此整个杨表有值的位置只有前 n \sqrt n n 行并上前 n \sqrt n n 列。
void Insert(int x,int y,int val)
{
y=min(y,a[x][0]);
while(y&&a[x][y]>val) y--;
y++;
if(y>a[x][0]) a[x][++a[x][0]]=val;
else Insert(x+1,y,a[x][y]),a[x][y]=val;
}
类似堆删除的思想,每次用矩阵最大元素代替删除元素,然后调整矩阵。
Remark: 发现求杨表的过程相当于贪心求解最长上升子序列。因此杨表的第一行长度就是最长上升子序列,第二行可以看做被第一行踢下去的元素求解最长上升子序列(踢下去表示第一行有更优的元素代替它)。以此类推。因此杨表可以做类似“取出 k 个不相交的上升子序列使得总长尽可能长”这样的问题。
给定一个序列,若干次询问,每次给出 m,k,找出 b 1 , . . . , m b_{1,...,m} b1,...,m 这个前缀最长的子序列,使得子序列的最长上升子序列不超过 k。
首先最长反链等于最小链覆盖,因此等价于找出 k 个互不相交的不升子序列,使得它们的总长度最长。这就相当于建一个“反杨表”( a i , j ≥ a i + 1 , j , a i , j ≥ a i , j + 1 a_{i,j}\geq a_{i+1,j},a_{i,j}\geq a_{i,j+1} ai,j≥ai+1,j,ai,j≥ai,j+1),然后把元素一个一个插进去,前 k 行就是答案。根据性质2,可以发现只要记录杨表的前根号行和前根号列即可。
那么我们就要维护杨表转置的前根号行。众所周知,把序列倒着插入(比较的时候加个等号)就可以得到杨表的转置。但是这个题显然不能这样做。一个神奇的性质是把上面插入代码中第四行 while(y&&a[x][y]>val) y--;
改为 while(y&&a[x][y]<=val) y--;
就可以得到一个和转置长得一样的矩阵(不是转置),所以我们只需要这个东西就行了。
注意插入的时候要对于每行二分,否则复杂度退化为 O ( n ) O(n) O(n)。不过好像数据不是很强,暴力插入也能过?
#include
#define ll long long
#define pb push_back
#define fir first
#define sec second
#define ld long double
using namespace std;
const int N=50010,M=500010;
typedef pair P;
int B,c[N],ksj[M],n,q,a[N];
vector b[N];
void add(int x) {while(x<=n) c[x]++,x+=x&-x;}
int get(int x) {int ans=0; while(x) ans+=c[x],x-=x&-x; return ans;}
struct Table1
{
int a[300][N];
void insert(int x,int y,int val)
{
if(x>B) return;
int l=1,r=min(y,a[x][0]),ans=r+1;
while(l<=r)
{
int mid=l+r>>1;
if(a[x][mid]B) return;
int l=1,r=min(y,a[x][0]),ans=r+1;
while(l<=r)
{
int mid=l+r>>1;
if(a[x][mid]>=val) r=mid-1,ans=mid;
else l=mid+1;
}
if(a[x][0]B) ans+=get(k)-get(B);
ksj[b[i][j].sec]=ans;
}
}
for(int i=1;i<=q;i++) cout<
n*m 的矩阵,定义一个矩阵有单调性当且仅当取两行两列,如果 a a . . . b . . . . . . c . . . d a...b\\ ......\\ c...d a...b......c...d
对于单调矩阵,每次询问一个点的值,我们可以用 O ( n + m ) O(n+m) O(n+m) 次询问找出每一行的最大值。
一个性质:矩阵满足单调性的充要条件是对于任意 2 × 2 2\times 2 2×2 的矩阵,都有主对角线的和大于等于另一条对角线的和。
分两个步骤:
T ( n ) = T ( n / 2 ) + O ( n ) = O ( n ) T(n)=T(n/2)+O(n)=O(n) T(n)=T(n/2)+O(n)=O(n)