杨表学习笔记
杨表学习笔记
简介
杨表(Young tableau)是一种常用于表示论和舒伯特演算中的组合对象,在数学中被用于对称群和一般线性群的研究。阿尔弗雷德·杨(Alfred Young)于 1900 年提出了杨表。
杨图
定义
杨图(Young diagram)又称 Ferrers 图,是一个有限的单元格集合,其中每一行左对齐,且每一行的长度不严格递减(也有一种画法是不严格递增)。
设杨图的总单元格数为 ,则每一行的格数构成了 的一种整数分拆 。我们可以用 表示杨图。
例如, 的杨图如下:
杨图中的每一个单元格用行数和列数唯一确定。在本文的画法中,行数、列数分别是从上往下、从左往右数;而在刚刚提到的另一种画法中,行数是从下往上数。换句话说,行数按单元格数从多到少的方向,列数从左对齐的方向。
对于 个单元格的杨图 和 个单元格的杨图 ,若 ,记 。
臂长、腿长、勾长
杨图的每个单元格 有臂长(arm length)、腿长(leg length)、勾长(hook length):
- 臂长为这个单元格右面的单元格个数,记作 。
- 腿长为这个单元格下面的单元格个数,记作 。
- 勾长为这个单元格及其右面、下面的单元格总数,记作 。由定义有 。
杨表
定义
杨表(Young tableau)是用某个字母表的符号填充杨图得到的,字母表通常需要是全序集合。为了方便,一般填入正整数。
标准杨表(standard Young tableau)是满足每列数字严格递增、每行数字严格递增的杨表。
半标准杨表(semistandard / column-strict Young tableau)是满足每列数字严格递增、每行数字非严格递增的杨表。
标准杨表的 RSK 插入算法
RSK 插入算法由 Robinson、Schensted、Knuth 提出,它可以将杨表和排列联系起来。
要将 插入杨表 中,算法流程如下:
- 移动到第一行。
- 在该行中找到比 大的最小数 。
- 如果 存在,将原本是 的单元格替换为 ,然后令 ,移动到下一行并重复步骤二。
- 如果 不存在,将 放到该行末尾。
容易证明在上述算法过后, 仍然是标准杨表。
杨表性质
将排列 按 RSK 插入算法依次插入得到杨表 ,有性质:
- 第一行长度 为排列的 LIS 长度(内容不一定)。
- 第一列长度为排列的 LDS 长度(内容不一定)。
- 若将排列 插入杨表 ,则 是 交换行列得到。
- 更换全序集合的比较方式 为 ,杨表 的形状是 交换行列得到(内容不一定)。
一些结论
勾长公式(hook length formula)
设有 个单元格的杨图 ,在其中填入 的排列,得到的标准杨表数为 ,则有:
例如,对于杨图 ,有:
证明
(参考 杨表和钩子公式)
设 为 的单元格,记 ,则 。
显然 。设 ,我们规定 ,只需证明 即可归纳得 。
对 变形得 。
定义勾行走(hook walk):
- 随机选取单元格 作为起点。
- 每次从 等概率地走到它下面、右面的共 格中的一个随机格子。
- 若不能再行走(即走到某个 ),则行走结束。
设随机事件 表示终点为 ,显然有 ,只需证 。
注意到 ,构造矩阵 :
构造矩阵 :
引理:。
证明
容易验证 成立。
有 。设 ,可以归纳知 和 时上式成立。
若 上式均成立,则:
引理得证。
对于任意 ,令 ,则有 , 为从 走到 的概率。
令 ,则:
Robinson–Schensted correspondence
任意两个相同形状的杨表(填数可能不同),可以与排列建立一一对应。
即:
其中 为 的所有分拆数,有结论 (A000041)。
证明(构造)
排列到双杨表的映射:
维护插入杨表 和记录杨表 。
依次枚举排列 中的元素 ,使用 RSK 插入算法将其插入 中。此时 中多了一个单元格,在 的相同位置添加一个单元格并填入 。
例如, 得到的两个杨表如下:
双杨表到排列的映射:
根据填数从大到小枚举 的单元格,从后往前确定排列 。
枚举到一个单元格,在 中找到对应的单元格。若在第一行则直接删除,否则在上一行找到比它小的最大数,将它放到那里并继续删除被替换的数。
其实就是 RSK 插入算法的逆过程。
题目
CF1268B Domino for Young
给定一个杨图,求最多放多少不重叠的骨牌。
。
题解
黑白染色,答案为更少的那种颜色个数。
证明
引理:黑白相等的杨图可以被骨牌密铺。
证明
放一个骨牌相当于删除两个相邻单元格。
若杨图为空,显然成立。
假设杨图有黑白格各 个时成立,下证黑白格各 个时成立。
若杨图存在两行格数相等,或存在两列格数相等,可以删除行/列末尾各一个单元格。显然可以找到这样的一对单元格,使得存在一个单元格 满足 ,从而使得剩余部分仍然是杨图。
否则, 黑白格数必然不等。
当黑白不相等时,先按上述过程删成 ,我们删除顶端的格子然后继续做即可。显然顶端的格子一定是更多的那种颜色。
复杂度为 。
代码
// Problem: CF1268B Domino for Young
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/CF1268B
// Memory Limit: 250 MB
// Time Limit: 3000 ms
//
// Powered by CP Editor (https://cpeditor.org)
//By: OIer rui_er
#include <bits/stdc++.h>
#define rep(x,y,z) for(ll x=(y);x<=(z);x++)
#define per(x,y,z) for(ll x=(y);x>=(z);x--)
#define debug printf("Running %s on line %d...\n",__FUNCTION__,__LINE__)
#define fileIO(s) do{freopen(s".in","r",stdin);freopen(s".out","w",stdout);}while(false)
using namespace std;
typedef long long ll;
const ll N = 3e5+5;
ll n, a[N], cnt[2];
template<typename T> void chkmin(T& x, T y) {if(x > y) x = y;}
template<typename T> void chkmax(T& x, T y) {if(x < y) x = y;}
int main() {
scanf("%lld", &n);
rep(i, 1, n) {
scanf("%lld", &a[i]);
cnt[i&1] += a[i] >> 1;
cnt[(i&1)^1] += a[i] - (a[i] >> 1);
}
printf("%lld\n", min(cnt[0], cnt[1]));
return 0;
}
P4484 [BJWC2018]最长上升子序列
求长度为 的随机排列 LIS 长度的期望。
。
题解
根据上文 Robinson–Schensted correspondence 中设计的映射算法,我们可以将排列一一对应为一对相同形状的杨表,其中杨表的第一行长度为 LIS 长度。
而对于杨图 ,其杨表种类数为 ,一对这种形状的杨表种类数为 ,杨表第一行长度为 。
于是所求被我们转化为:
我们有勾长公式,可以在 时间计算 :
另外前文提到,,可以直接枚举。
于是复杂度为 。
代码
// Problem: P4484 [BJWC2018]最长上升子序列
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P4484
// Memory Limit: 500 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
//By: OIer rui_er
#include <bits/stdc++.h>
#define rep(x,y,z) for(ll x=(y);x<=(z);x++)
#define per(x,y,z) for(ll x=(y);x>=(z);x--)
#define debug(format...) fprintf(stderr, format)
#define fileIO(s) do{freopen(s".in","r",stdin);freopen(s".out","w",stdout);}while(false)
using namespace std;
typedef long long ll;
const ll N = 30, mod = 998244353;
ll n, inv[N], fac, ifac, a[N], cnt[N], ans;
template<typename T> void chkmin(T& x, T y) {if(x > y) x = y;}
template<typename T> void chkmax(T& x, T y) {if(x < y) x = y;}
ll calc(ll m) {
ll ans = fac;
rep(i, 1, m) rep(j, 1, a[i]) ans = ans * inv[a[i]-j+cnt[j]-i+1] % mod;
return ans;
}
void dfs(ll u, ll rem, ll lim) {
if(!rem) {
ll now = calc(u-1);
ans = (ans + now * now % mod * a[1] % mod) % mod;
return;
}
rep(i, 1, min(rem, lim)) {
a[u] = i; ++cnt[i];
dfs(u+1, rem-i, i);
}
rep(i, 1, min(rem, lim)) --cnt[i];
}
int main() {
scanf("%lld", &n);
fac = ifac = inv[0] = inv[1] = 1;
rep(i, 2, n) {
fac = fac * i % mod;
inv[i] = (mod - mod / i) * inv[mod%i] % mod;
ifac = ifac * inv[i] % mod;
}
dfs(1, n, n);
ans = ans * ifac % mod;
printf("%lld\n", ans);
return 0;
}
P3774 [CTSC2017]最长上升子序列
给定长度为 的数列 ,进行 次询问,每次询问一个 的 前缀的最长的满足 LIS 长度不超过 的子序列长度。
,。
题解·上( 分)
考虑扫描线,对询问按照 升序离线,每次加入一个数,问题转化为求当前数列的最长的满足 LIS 长度不超过 的子序列长度。
根据 Dilworth 定理(或者是对偶定理?分不清楚),对于有限偏序集,最长链中元素个数等于最小反链划分中反链个数。我们可以知道 LIS 长度不超过 等价于用不超过 个 DS 覆盖,从而将所求转化为杨表的前 列元素个数。
我们使用 RSK 插入算法维护杨表。这里虽然不是标准杨表,但是可以改造该算法使其能维护半标准杨表。然后使用树状数组维护每一列元素个数。
注意到 RSK 插入算法的复杂度为 ,因此这个算法的最坏复杂度为 ,实际得分 分。
代码·上( 分)
// Problem: P3774 [CTSC2017]最长上升子序列
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3774
// Memory Limit: 500 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
//By: OIer rui_er
#include <bits/stdc++.h>
#define rep(x,y,z) for(int x=(y);x<=(z);x++)
#define per(x,y,z) for(int x=(y);x>=(z);x--)
#define debug(format...) fprintf(stderr, format)
#define fileIO(s) do{freopen(s".in","r",stdin);freopen(s".out","w",stdout);}while(false)
using namespace std;
typedef long long ll;
const int N = 2e5+5;
int n, m, a[N], ans[N];
vector<vector<int>> young;
template<typename T> void chkmin(T& x, T y) {if(x > y) x = y;}
template<typename T> void chkmax(T& x, T y) {if(x < y) x = y;}
struct Query {
int m, k, id;
}q[N];
struct BIT {
int c[N];
int lowbit(int x) {return x & (-x);}
void add(int x, int k) {for(; x < N; x += lowbit(x)) c[x] += k;}
int ask(int x) {int k = 0; for(; x; x -= lowbit(x)) k += c[x]; return k;}
}cnt;
void insert(int x) {
int sz = young.size();
if(!sz) {
young.push_back({x});
cnt.add(1, 1);
}
else {
for(auto& i : young) {
auto it = lower_bound(i.begin(), i.end(), x);
if(it == i.end()) {
i.push_back(x);
cnt.add(i.size(), 1);
return;
}
swap(*it, x);
}
young.push_back({x});
cnt.add(1, 1);
}
}
int main() {
scanf("%d%d", &n, &m);
rep(i, 1, n) scanf("%d", &a[i]);
rep(i, 1, m) {
scanf("%d%d", &q[i].m, &q[i].k);
q[i].id = i;
}
sort(q+1, q+1+m, [](const auto& a, const auto& b) {
return a.m < b.m;
});
int j = 1;
rep(i, 1, n) {
insert(a[i]);
for(; j <= m && q[j].m == i; j++) ans[q[j].id] = cnt.ask(q[j].k);
}
rep(i, 1, m) printf("%d\n", ans[i]);
return 0;
}
题解·下( 分)
显然杨表的前 行和前 列可以覆盖整个杨表,考虑维护 关系的原杨表和 关系的转置杨表,分别维护前 行。
每次加入元素就在两个杨表里同时插入,不过 RSK 插入算法只枚举前 行即可,加到树状数组中的时候处理一下不要加重。
复杂度 。
代码·下( 分)
// Problem: P3774 [CTSC2017]最长上升子序列
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3774
// Memory Limit: 500 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
//By: OIer rui_er
#include <bits/stdc++.h>
#define rep(x,y,z) for(int x=(y);x<=(z);x++)
#define per(x,y,z) for(int x=(y);x>=(z);x--)
#define debug(format...) fprintf(stderr, format)
#define fileIO(s) do{freopen(s".in","r",stdin);freopen(s".out","w",stdout);}while(false)
using namespace std;
typedef long long ll;
const int N = 2e5+5;
int n, m, B, a[N], ans[N];
vector<vector<int>> youngA, youngR;
template<typename T> void chkmin(T& x, T y) {if(x > y) x = y;}
template<typename T> void chkmax(T& x, T y) {if(x < y) x = y;}
struct Query {
int m, k, id;
}q[N];
struct BIT {
int c[N];
int lowbit(int x) {return x & (-x);}
void add(int x, int k) {for(; x < N; x += lowbit(x)) c[x] += k;}
int ask(int x) {int k = 0; for(; x; x -= lowbit(x)) k += c[x]; return k;}
}cnt;
void insertA(int x) {
int szA = youngA.size();
if(!szA) youngA.push_back({x});
else {
for(auto& i : youngA) {
auto it = lower_bound(i.begin(), i.end(), x);
if(it == i.end()) {
i.push_back(x);
if((int)i.size() > B) cnt.add(i.size(), 1);
return;
}
swap(*it, x);
}
if(szA < B) youngA.push_back({x});
}
}
void insertR(int x) {
int szR = youngR.size();
if(!szR) {
youngR.push_back({x});
cnt.add(1, 1);
}
else {
int i = 0;
for(auto& r : youngR) {
++i;
auto it = upper_bound(r.begin(), r.end(), x, greater<int>());
if(it == r.end()) {
r.push_back(x);
cnt.add(i, 1);
return;
}
swap(*it, x);
}
if(szR < B) {
youngR.push_back({x});
cnt.add(i+1, 1);
}
}
}
void insert(int x) {
insertA(x);
insertR(x);
}
int main() {
scanf("%d%d", &n, &m); B = sqrt(n);
rep(i, 1, n) scanf("%d", &a[i]);
rep(i, 1, m) {
scanf("%d%d", &q[i].m, &q[i].k);
q[i].id = i;
}
sort(q+1, q+1+m, [](const auto& a, const auto& b) {
return a.m < b.m;
});
int j = 1;
rep(i, 1, n) {
insert(a[i]);
for(; j <= m && q[j].m == i; j++) ans[q[j].id] = cnt.ask(q[j].k);
}
rep(i, 1, m) printf("%d\n", ans[i]);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
2022-01-24 题解 SP25844 / CF276D【MAXXOR - Find the max XOR value / Little Girl and Maximum XOR】