两种特殊的矩阵(杨氏矩阵及其应用)

杨氏矩阵

部分参考凄魉博客

性质

杨氏矩阵(杨表)是这样的二维数组,满足 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,jai+1,j,ai,jai,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=fn1+(n1)fn2。这个递推式的意义不好解释,但是对于另一个问题,这个递推式就显而易见了,这个问题就是 n 个点任意匹配的方案数。这似乎说明了,这两个问题似乎存在一种对应关系?

我们来证明这个递推式。首先,如果我们有一个大小为 n-1 的杨表,我们一定可以把 n 放到最后一行得到大小为 n 的杨表。这就得到了递推式的前一项。接下来,如果我们有一个 1~n-1 的数 j 和一个 n-1 的杨表,我们同样能唯一确定一个大小为 n 的杨表,方法是这样的:

  • 把杨表中元素 j~n-1 全部 +1,得到一个包含 [1~j-1] 和 [j+1,n] 的杨表。
  • 把 j 插入到杨表中(下面“实现”中的”插入“)。

容易发现操作是可逆的,也就是说 (j,n-1 的杨表) 这个二元组和 n 的杨表是一一对应的。我们的递推式得证。

下面不加证明地给出几个结论:

  • 两个形状相同大小为 n 的杨表对应一个 n 的置换。
  • 两个完全相同大小为 n 的杨表对应一个逆等于自身的置换(这也就说明了为什么 n 个点杨表数量等于 n 个点随意匹配的方案数。
  • 如果 (a,b) 对应置换 P,那么 (b,a) 对应置换 P-1

实现

查询

根据杨表的性质,要查询某个元素 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,jai+1,j,ai,jai,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<bits/stdc++.h>
#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 <int,int> P;
int B,c[N],ksj[M],n,q,a[N];
vector <P> 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]<val) r=mid-1,ans=mid;
			else l=mid+1;
		}
		if(a[x][0]<ans) a[x][++a[x][0]]=val;
		else insert(x+1,ans,a[x][ans]),a[x][ans]=val;
	}
}A;
struct Table2 
{
	int a[310][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]>=val) r=mid-1,ans=mid;
			else l=mid+1;
		}
		if(a[x][0]<ans) a[x][++a[x][0]]=val,add(a[x][0]);
		else insert(x+1,ans,a[x][ans]),a[x][ans]=val;
	}
}Ar;
int read()
{
	int x=0;char c=getchar(),flag='+';
	while(!isdigit(c)) flag=c,c=getchar();
	while(isdigit(c)) x=x*10+c-'0',c=getchar();
	return flag=='-'?-x:x;
}
int main()
{
	n=read(),q=read(),B=sqrt(n)+1;
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=1;i<=q;i++)
	{
		int m=read(),k=read();
		b[m].pb(P(k,i));
	}
	for(int i=1;i<=n;i++)
	{
		A.insert(1,n,a[i]);
		Ar.insert(1,n,a[i]);
		for(int j=0;j<b[i].size();j++)
		{
			int k=b[i][j].fir,ans=0;
			for(int e=1;e<=min(k,B);e++) ans+=A.a[e][0];
			if(k>B) ans+=get(k)-get(B);
			ksj[b[i][j].sec]=ans;
		}
	}
	for(int i=1;i<=q;i++) cout<<ksj[i]<<'\n';
	return 0;
}
/*by DT_Kang*/

一种单调性矩阵

n*m 的矩阵,定义一个矩阵有单调性当且仅当取两行两列,如果 a<b 那么 c<d。
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 的矩阵,都有主对角线的和大于等于另一条对角线的和。

做法

分两个步骤:

  • 当 n<m 时,可以用 n+m 的时间里删掉一些列,使得最大值还在剩下的矩阵里。
    比较第一行的前两个元素,如果 a<b ,可以直接删掉 a 的列。否则说明 b 以及上面的格子都是没有用的。我们维护一条阶梯形状的折线,上面是没有用的点。这样到了最后一行,如果 a>b,那么可以完整删掉b这一列。可以发现,操作完后矩阵变成一个方阵。
  • 当 n=m 时,把偶数行提取出来合并成一个新矩阵,递归处理。然后奇数行的答案一定夹在相邻的两个偶数行的答案之间,只需要进行 O ( n ) O(n) O(n) 次询问即可。

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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值