兩種特殊的矩陣(楊氏矩陣及其應用)

楊氏矩陣

部分參考悽魎博客

性質

楊氏矩陣(楊表)是這樣的二維數組,滿足 ai,jai+1,j,ai,jai,j+1a_{i,j}\leq a_{i+1,j},a_{i,j}\leq a_{i,j+1}。以 a1,1a_{1,1} 爲根的話,楊表滿足堆的性質。以 a1,ma_{1,m} 爲根,滿足平衡樹的性質。下圖就是一個楊表:
在這裏插入圖片描述

可以發現,楊表不一定是一個矩形。我們接下來解決兩個問題:

第一個問題,給定一個階梯型矩陣,共有 n 個位置,求把 1~n 填進去構成楊氏矩陣的數量。

根據鉤子定理,答案等於 n!hi,j\frac {n!} {\prod h_{i,j}}hi,jh_{i,j} 定義爲 (i,j) 這個格子下面的格子數+右邊的格子數+1。下圖格子裏的數標註的就是每個格子的 h 值:
在這裏插入圖片描述
我們感性理解一下這個結論:把這 n 個數隨機填入矩陣,每個格子是它下面以及右邊格子的最小值的概率是 1hi,j\frac 1 {h_{i,j}},那麼把每個格子的概率乘起來大概就能得到我們的鉤子定理了。雖然結論正確,我們的證明確實有疏漏的,因爲這些事件不是相互獨立的。

我們來解決第二個問題:n 個元素的楊表一共有多少種?先說結論:設 fnf_n 表示 n 個元素楊表的數量,那麼有 fn=fn1+(n1)fn2f_n=f_{n-1}+(n-1)f_{n-2}。這個遞推式的意義不好解釋,但是對於另一個問題,這個遞推式就顯而易見了,這個問題就是 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)

插入

我們把一個元素 val 插入到楊表中,需要調用 Insert(1,inf,val) 。類似查詢一個數的方式即可。由於楊表第 i 行元素個數大於等於第 i+1 行元素個數。因此整個楊表有值的位置只有前 n\sqrt n 行並上前 n\sqrt 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,找出 b1,...,mb_{1,...,m} 這個前綴最長的子序列,使得子序列的最長上升子序列不超過 k。

首先最長反鏈等於最小鏈覆蓋,因此等價於找出 k 個互不相交的不升子序列,使得它們的總長度最長。這就相當於建一個“反楊表”(ai,jai+1,j,ai,jai,j+1a_{i,j}\geq a_{i+1,j},a_{i,j}\geq a_{i,j+1}),然後把元素一個一個插進去,前 k 行就是答案。根據性質2,可以發現只要記錄楊表的前根號行和前根號列即可。

那麼我們就要維護楊錶轉置的前根號行。衆所周知,把序列倒着插入(比較的時候加個等號)就可以得到楊表的轉置。但是這個題顯然不能這樣做。一個神奇的性質是把上面插入代碼中第四行 while(y&&a[x][y]>val) y--; 改爲 while(y&&a[x][y]<=val) y--; 就可以得到一個和轉置長得一樣的矩陣(不是轉置),所以我們只需要這個東西就行了。

注意插入的時候要對於每行二分,否則複雜度退化爲 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

對於單調矩陣,每次詢問一個點的值,我們可以用 O(n+m)O(n+m) 次詢問找出每一行的最大值。

一個性質:矩陣滿足單調性的充要條件是對於任意 2×22\times 2 的矩陣,都有主對角線的和大於等於另一條對角線的和。

做法

分兩個步驟:

  • 當 n<m 時,可以用 n+m 的時間裏刪掉一些列,使得最大值還在剩下的矩陣裏。
    比較第一行的前兩個元素,如果 a<b ,可以直接刪掉 a 的列。否則說明 b 以及上面的格子都是沒有用的。我們維護一條階梯形狀的折線,上面是沒有用的點。這樣到了最後一行,如果 a>b,那麼可以完整刪掉b這一列。可以發現,操作完後矩陣變成一個方陣。
  • 當 n=m 時,把偶數行提取出來合併成一個新矩陣,遞歸處理。然後奇數行的答案一定夾在相鄰的兩個偶數行的答案之間,只需要進行 O(n)O(n) 次詢問即可。

T(n)=T(n/2)+O(n)=O(n)T(n)=T(n/2)+O(n)=O(n)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章