数据结构与算法:有序表(四):笛卡尔树与 Treap
前言越来越难了……希望区域赛有个好结果吧加油加油加油一、笛卡尔树1.性质笛卡尔树有两个属性 key 和 value一般默认 key 没有重复值。此时笛卡尔树满足 key 按搜索二叉树组织value 按堆组织。2.建立在笛卡尔树的建立过程中用单调栈维护节点的 value即树上从顶到底的一条链。所以如果要求 value 是小根堆那么就让单调栈内大压小。每当有节点入栈那么就让其成为栈顶节点的右孩子。在上图中例子在考虑完前三个位置后此时单调栈内是如图所示所以就在树上建出了这条链。当 (4,4) 这个节点来到时由于 4 小于 8 和 6此时就让最后一个弹出的节点和其父亲断连让其作为左孩子挂在当前节点的下面然后再让当前节点挂在此时的栈顶节点的右孩子上的。所以此时 (2,6) 和 (3,8) 这条链就需要成为 (4,4) 的左孩子然后 (4,4) 成为 (1,3) 的右孩子。之后当 (5,7) 来到时还是成为 (4,4) 的右孩子。可以发现由于后来的 key 肯定比前面的大所以在插入节点时往左挂必然就满足搜索二叉树的性质。又因为是单调栈所以上方的 value 必然小于下方的 value也满足堆的性质。在 (6,1) 来到时此时所有的节点都需要弹出那么 (6,1) 就成为当前树的头节点其他节点都挂在其左子树上。在 (7,5) 来到时还是直接成为 (6,1) 的右孩子。在 (8,2) 来到时就让 (7,5) 挂在其左子树上然后其成为 (6,1) 的右孩子整个过程结束。此时栈中最底下的节点就是笛卡尔树的头节点。二、Treap 树Treap 树就是笛卡尔树key 还是按二叉搜索树组织priority 表示节点优先级随机生成按堆组织。当 priority 不满足堆的性质时就通过左旋或右旋一直往上调整即可。删除时若要删除节点是叶子那么就直接删除。若有左无右或有右五左那么就让那个孩子接替自己即可。而若两边都有那么就通过左旋或右旋让 priority 大的那个孩子上来然后再去子树删除这个节点。三、笛卡尔树题目1.笛卡尔树#include bits/stdc.h using namespace std; /* /\_/\ * ( ._.) * / \ */ /* *想好再写 *注意审题 注意特判 *不要红温 不要急躁 耐心一点 *WA了不要立马觉得是思路不对 先耐心找反例 */ #define endl \n #define dbg(x) cout#xendl;coutxendl; #define vdbg(a) cout#aendl;for(auto x:a)coutx ;coutendl; #define YES coutYESendl;return ; #define Yes coutYesendl;return ; #define NO coutNOendl;return ; #define No coutNoendl;return ; #define popcount __builtin_popcount using lllong long; using i128__int128; using ldlong double; using piipairint,int; using pllpairll,ll; const int INF1e9; const ll INFLL1e18; const int dx[]{-1,1,0,0}; const int dy[]{0,0,-1,1}; const int ddx[]{-2,-1,1,2,2,1,-1,-2}; const int ddy[]{1,2,2,1,-1,-2,-2,-1}; void solve() { int n; cinn; vectorinta(n1); for(int i1;in;i) { cina[i]; } vectorintleft(n1); vectorintright(n1); vectorintstk(n1); for(int i1,top0,pos0;in;i){ postop; while(pos0a[stk[pos]]a[i]){ pos--; } if(pos0){ right[stk[pos]]i; } if(postop){ left[i]stk[pos1]; } stk[pos]i; toppos; } ll ans10,ans20; for(int i1;in;i) { ans1^1ll*i*(left[i]1); ans2^1ll*i*(right[i]1); } coutans1 ans2endl; } void init() { } signed main() { ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); int t1; //cint; init(); while(t--) { solve(); } return 0; }在构建的过程中每次用 pos 虚拟弹出节点。最后若还有节点没弹出就修改其右孩子若有东西弹出了就设置左孩子即可。2.树的序#include bits/stdc.h using namespace std; /* /\_/\ * ( ._.) * / \ */ /* *想好再写 *注意审题 注意特判 *不要红温 不要急躁 耐心一点 *WA了不要立马觉得是思路不对 先耐心找反例 */ #define endl \n #define dbg(x) cout#x xendl; #define vdbg(a) cout#aendl;for(auto x:a)coutx ;coutendl; #define YES coutYESendl;return ; #define Yes coutYesendl;return ; #define NO coutNOendl;return ; #define No coutNoendl;return ; #define popcount __builtin_popcount using lllong long; using i128__int128; using ldlong double; using piipairint,int; using pllpairll,ll; const int INF1e9; const ll INFLL1e18; const int dx[]{-1,1,0,0}; const int dy[]{0,0,-1,1}; const int ddx[]{-2,-1,1,2,2,1,-1,-2}; const int ddy[]{1,2,2,1,-1,-2,-2,-1}; void solve() { int n; cinn; vectorintp(n1); for(int i1;in;i) { cinp[i]; } vectorinta(n1); for(int i1;in;i) { a[p[i]]i; } vectorintleft(n1); vectorintright(n1); vectorintstk(n1); for(int i1,top0,pos0;in;i){ postop; //small heap while(pos0a[stk[pos]]a[i]){ pos--; } if(pos0){ right[stk[pos]]i; } if(postop){ left[i]stk[pos1]; } stk[pos]i; toppos; } auto dfs[](auto self,int u)-void { coutu ; if(left[u]) { self(self,left[u]); } if(right[u]) { self(self,right[u]); } }; dfs(dfs,p[1]); } void init() { } signed main() { ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); int t1; //cint; init(); while(t--) { solve(); } return 0; }对于建出的这棵树其实直接先序遍历一下就是字典序最小的结果。但由于可能给定的序列就是单调的此时就会导致每次建树的复杂度过大需要考虑优化。由于笛卡尔树建树的复杂度是线性的所以考虑用笛卡尔树优化。由于原来的树中的节点 x必然有其祖先节点出现的位置都在 x 出现位置的前面。那么就可以将下标看作时间戳此时就要求时间戳小的在树的上面。又因为笛卡尔树是权值维度 key 按堆组织时间戳维度按二叉搜索树组织而在这里需要让权值按二叉搜索树组织时间戳按堆组织所以交换一下构建笛卡尔树即可。3.E. Yet Another Array Counting Problem#include bits/stdc.h using namespace std; /* /\_/\ * ( ._.) * / \ */ /* *想好再写 *注意审题 注意特判 *不要红温 不要急躁 耐心一点 *WA了不要立马觉得是思路不对 先耐心找反例 */ #define endl \n #define dbg(x) cout#x xendl; #define vdbg(a) cout#aendl;for(auto x:a)coutx ;coutendl; #define YES coutYESendl;return ; #define Yes coutYesendl;return ; #define NO coutNOendl;return ; #define No coutNoendl;return ; #define popcount __builtin_popcount using lllong long; using i128__int128; using ldlong double; using piipairint,int; using pllpairll,ll; const int INF1e9; const ll INFLL1e18; const int dx[]{-1,1,0,0}; const int dy[]{0,0,-1,1}; const int ddx[]{-2,-1,1,2,2,1,-1,-2}; const int ddy[]{1,2,2,1,-1,-2,-2,-1}; templateclass T constexpr T power(T a, ll b) { T res 1; for (; b ! 0; b / 2, a * a) { if (b 1) { res * a; } } return res; } templateint M struct ModInt { public: constexpr ModInt() : x(0) {} templatetypename T constexpr ModInt(T x_) { T v x_ % M; if (v 0) { v M; } x v; } constexpr int val() const { return x; } constexpr ModInt operator() { x; if (x M) { x 0; } return *this; } constexpr ModInt operator(int) { ModInt res *this; (*this); return res; } constexpr ModInt operator--() { if (x 0) { x M - 1; } else { x--; } return *this; } constexpr ModInt operator--(int) { ModInt res *this; --(*this); return res; } constexpr ModInt operator-() const { ModInt res; res.x (x 0 ? 0 : M - x); return res; } constexpr ModInt inv() const { return power(*this, M - 2); } constexpr ModInt operator*(const ModInt rhs) { x ll(x) * rhs.val() % M; return *this; } constexpr ModInt operator(const ModInt rhs) { x rhs.val(); if (x M) { x - M; } return *this; } constexpr ModInt operator-(const ModInt rhs) { x - rhs.val(); if (x 0) { x M; } return *this; } constexpr ModInt operator/(const ModInt rhs) { return *this * rhs.inv(); } friend constexpr ModInt operator*(ModInt lhs, const ModInt rhs) { lhs * rhs; return lhs; } friend constexpr ModInt operator(ModInt lhs, const ModInt rhs) { lhs rhs; return lhs; } friend constexpr ModInt operator-(ModInt lhs, const ModInt rhs) { lhs - rhs; return lhs; } friend constexpr ModInt operator/(ModInt lhs, const ModInt rhs) { lhs / rhs; return lhs; } friend constexpr bool operator(ModInt lhs, const ModInt rhs) { return lhs.val() rhs.val(); } friend constexpr bool operator(ModInt lhs, const ModInt rhs) { return lhs.val() rhs.val(); } friend constexpr bool operator(ModInt lhs, const ModInt rhs) { return lhs.val() rhs.val(); } friend constexpr bool operator(ModInt lhs, const ModInt rhs) { return lhs.val() rhs.val(); } friend constexpr bool operator(ModInt lhs, const ModInt rhs) { return lhs.val() rhs.val(); } friend constexpr bool operator!(ModInt lhs, const ModInt rhs) { return lhs.val() ! rhs.val(); } friend constexpr std::istream operator(std::istream is, ModInt a) { ll i; is i; a i; return is; } friend constexpr std::ostream operator(std::ostream os, const ModInt a) { return os a.val(); } private: int x; }; templateint M, typename T ModIntM struct Comb { vectorT fac; vectorT inv; Comb(int n) { fac.assign(n, 1); for(int i1;in;i) { fac[i]fac[i-1]*i; } inv.assign(n, 1); inv[n-1]fac[n-1].inv(); for(int in-2;i0;i--) { inv[i]inv[i1]*(i1); } } templatestd::signed_integral U T P(U n, U m) { if(nm) { return 0; } return fac[n] * inv[n - m]; } templatestd::signed_integral U T C(U n, U m) { if(nm||m0) { return 0; } return fac[n] * inv[n - m] * inv[m]; } }; //power函数切记强转成 Z !!!!! constexpr int M 1e97; using Z ModIntM; constexpr int N 2e55; CombMcomb(N); templatestd::signed_integral U Z P(U n, U m) { return comb.P(n, m); } templatestd::signed_integral U Z C(U n, U m) { return comb.C(n, m); } void solve() { int n,m; cinnm; vectorinta(n1); for(int i1;in;i) { cina[i]; } vectorintleft(n1); vectorintright(n1); vectorintstk(n1); for(int i1,top0,pos0;in;i){ postop; //big heap while(pos0a[stk[pos]]a[i]){ pos--; } if(pos0){ right[stk[pos]]i; } if(postop){ left[i]stk[pos1]; } stk[pos]i; toppos; } int rootstk[1]; vectorvectorZdp(n1,vectorZ(m1)); for(int j0;jm;j) { dp[0][j]1; } auto dfs[](auto self,int u)-void { if(left[u]) { self(self,left[u]); } if(right[u]) { self(self,right[u]); } for(int j1;jm;j) { dp[u][j]dp[u][j-1]dp[left[u]][j-1]*dp[right[u]][j]; } }; dfs(dfs,root); coutdp[root][m]endl; } void init() { } signed main() { ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); int t1; cint; init(); while(t--) { solve(); } return 0; }这里就有一个性质笛卡尔树的 LCA 就是区间 RMQ。那么在对原数组建立大根堆组织的笛卡尔树后此时每个区间的最左端最大值就是区间两端点在笛卡尔树中的 LCA。那么若此时的根节点选择了数字 x即这个位置在 b 数组中是 x那么之后左孩子代表的位置就只能填小于 x 的数右孩子代表的位置就只能填小于等于 x 的数。这个的方案数可以在 O(nm) 内通过树型 dp 解决。在去笛卡尔树上 dp 时考虑定义 dp[u][j] 为 u 节点值小于等于 j 的方案数。那么每次构建完每个节点值为 x 的方案数时求一下前缀和即可。4.PERIODNI这个题就展示了笛卡尔树划分可能性的用法了。#include bits/stdc.h using namespace std; /* /\_/\ * ( ._.) * / \ */ /* *想好再写 *注意审题 注意特判 *不要红温 不要急躁 耐心一点 *WA了不要立马觉得是思路不对 先耐心找反例 */ #define endl \n #define dbg(x) cout#x xendl; #define vdbg(a) cout#aendl;for(auto x:a)coutx ;coutendl; #define YES coutYESendl;return ; #define Yes coutYesendl;return ; #define NO coutNOendl;return ; #define No coutNoendl;return ; #define popcount __builtin_popcount using lllong long; using i128__int128; using ldlong double; using piipairint,int; using pllpairll,ll; const int INF1e9; const ll INFLL1e18; const int dx[]{-1,1,0,0}; const int dy[]{0,0,-1,1}; const int ddx[]{-2,-1,1,2,2,1,-1,-2}; const int ddy[]{1,2,2,1,-1,-2,-2,-1}; templateclass T constexpr T power(T a, ll b) { T res 1; for (; b ! 0; b / 2, a * a) { if (b 1) { res * a; } } return res; } templateint M struct ModInt { public: constexpr ModInt() : x(0) {} templatetypename T constexpr ModInt(T x_) { T v x_ % M; if (v 0) { v M; } x v; } constexpr int val() const { return x; } constexpr ModInt operator() { x; if (x M) { x 0; } return *this; } constexpr ModInt operator(int) { ModInt res *this; (*this); return res; } constexpr ModInt operator--() { if (x 0) { x M - 1; } else { x--; } return *this; } constexpr ModInt operator--(int) { ModInt res *this; --(*this); return res; } constexpr ModInt operator-() const { ModInt res; res.x (x 0 ? 0 : M - x); return res; } constexpr ModInt inv() const { return power(*this, M - 2); } constexpr ModInt operator*(const ModInt rhs) { x ll(x) * rhs.val() % M; return *this; } constexpr ModInt operator(const ModInt rhs) { x rhs.val(); if (x M) { x - M; } return *this; } constexpr ModInt operator-(const ModInt rhs) { x - rhs.val(); if (x 0) { x M; } return *this; } constexpr ModInt operator/(const ModInt rhs) { return *this * rhs.inv(); } friend constexpr ModInt operator*(ModInt lhs, const ModInt rhs) { lhs * rhs; return lhs; } friend constexpr ModInt operator(ModInt lhs, const ModInt rhs) { lhs rhs; return lhs; } friend constexpr ModInt operator-(ModInt lhs, const ModInt rhs) { lhs - rhs; return lhs; } friend constexpr ModInt operator/(ModInt lhs, const ModInt rhs) { lhs / rhs; return lhs; } friend constexpr bool operator(ModInt lhs, const ModInt rhs) { return lhs.val() rhs.val(); } friend constexpr bool operator(ModInt lhs, const ModInt rhs) { return lhs.val() rhs.val(); } friend constexpr bool operator(ModInt lhs, const ModInt rhs) { return lhs.val() rhs.val(); } friend constexpr bool operator(ModInt lhs, const ModInt rhs) { return lhs.val() rhs.val(); } friend constexpr bool operator(ModInt lhs, const ModInt rhs) { return lhs.val() rhs.val(); } friend constexpr bool operator!(ModInt lhs, const ModInt rhs) { return lhs.val() ! rhs.val(); } friend constexpr std::istream operator(std::istream is, ModInt a) { ll i; is i; a i; return is; } friend constexpr std::ostream operator(std::ostream os, const ModInt a) { return os a.val(); } private: int x; }; templateint M, typename T ModIntM struct Comb { vectorT fac; vectorT inv; Comb(int n) { fac.assign(n, 1); for(int i1;in;i) { fac[i]fac[i-1]*i; } inv.assign(n, 1); inv[n-1]fac[n-1].inv(); for(int in-2;i0;i--) { inv[i]inv[i1]*(i1); } } templatestd::signed_integral U T P(U n, U m) { if(nm) { return 0; } return fac[n] * inv[n - m]; } templatestd::signed_integral U T C(U n, U m) { if(nm||m0) { return 0; } return fac[n] * inv[n - m] * inv[m]; } }; //power函数切记强转成 Z !!!!! constexpr int M 1e97; using Z ModIntM; constexpr int N 1e65; CombMcomb(N); templatestd::signed_integral U Z P(U n, U m) { return comb.P(n, m); } templatestd::signed_integral U Z C(U n, U m) { return comb.C(n, m); } void solve() { int n,k; cinnk; vectorinta(n1); for(int i1;in;i) { cina[i]; } vectorintleft(n1); vectorintright(n1); vectorintstk(n1); for(int i1,top0,pos0;in;i){ postop; //small heap while(pos0a[stk[pos]]a[i]){ pos--; } if(pos0){ right[stk[pos]]i; } if(postop){ left[i]stk[pos1]; } stk[pos]i; toppos; } int rootstk[1]; auto calc[](int n,int m,int k)-Z { return C(n,k)*C(m,k)*comb.fac[k]; }; vectorintsiz(n1); vectorvectorZdp(n1,vectorZ(k1)); dp[0][0]1; auto dfs[](auto self,int u,int fa)-void { siz[u]1; if(left[u]) { self(self,left[u],u); siz[u]siz[left[u]]; } if(right[u]) { self(self,right[u],u); siz[u]siz[right[u]]; } vectorZtmp(k1); tmp[0]1; for(int i1;ik;i) { for(int x0,yi;xi;x,y--) { tmp[i]dp[left[u]][x]*dp[right[u]][y]; } } int rowa[u]-a[fa]; int colsiz[u]; dp[u][0]1; for(int i1;ik;i) { for(int x0,yi;xixcol;x,y--) { dp[u][i]tmp[x]*calc(row,col-x,y); } } }; dfs(dfs,root,0); coutdp[root][k]endl; } void init() { } signed main() { ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); int t1; //cint; init(); while(t--) { solve(); } return 0; }首先如果要将 k 个数填入 k*k 的区域方案数就是 k!。扩展一下若要想 k 个数填入 n*m 的区域方案数就是 C(n,k)*C(m,k)*k!即先从 n 行选 k 行再从 m 行选 k 行然后具体数的方案数是 k!。之后对数组建立小根堆组织的笛卡尔树然后根据树的结构划分每个节点负责的范围。对于当前节点 u若其父亲节点为 fa那么其负责的高度就是自己高度减去父亲高度负责的列数就是 u 的子树大小。原因是由于维护的小根堆那么从顶到底高度必然满足从低到高所以自然可以依次划分。而同样因为小根堆所以当前节点子树内的高度都是大于自己的又因为下标满足二叉搜索树那么子树内在列上肯定也是连续的所以列的长度必然就是子树大小。之后还是去笛卡尔树上 dp那么就是定义 dp[u][i] 为将 i 个数放到 u 这棵子树负责的区域的方案数初始 dp[0][0] 啥也不放有一种注意是整个子树而非只是当前区域。那么首先 dp[u][0] 不放数肯定有 1 种之后对于当前放的个数 i就是讨论在当前节点放 x 个在之前区域放 y 个使得 xyi 的方案。那么由于之前有了 n*m 区域放 k 个数的方案数这个就可以快速计算了。注意若之前区域放了数的话需要在列上减去放的个数因为已经被占用了。那么就只需要先计算左右孩子合并得到的之前区域方案数再和当前节点合并即可。5.B - Removing Blocks感觉在这种堆方块问题上笛卡尔树能很好的维护位置和高度或时间的有序关系。#include bits/stdc.h using namespace std; /* /\_/\ * ( ._.) * / \ */ /* *想好再写 *注意审题 注意特判 *不要红温 不要急躁 耐心一点 *WA了不要立马觉得是思路不对 先耐心找反例 */ #define endl \n #define dbg(x) cout#x xendl; #define vdbg(a) cout#aendl;for(auto x:a)coutx ;coutendl; #define YES coutYESendl;return ; #define Yes coutYesendl;return ; #define NO coutNOendl;return ; #define No coutNoendl;return ; #define popcount __builtin_popcount using lllong long; using i128__int128; using ldlong double; using piipairint,int; using pllpairll,ll; const int INF1e9; const ll INFLL1e18; const int dx[]{-1,1,0,0}; const int dy[]{0,0,-1,1}; const int ddx[]{-2,-1,1,2,2,1,-1,-2}; const int ddy[]{1,2,2,1,-1,-2,-2,-1}; templateclass T constexpr T power(T a, ll b) { T res 1; for (; b ! 0; b / 2, a * a) { if (b 1) { res * a; } } return res; } templateint M struct ModInt { public: constexpr ModInt() : x(0) {} templatetypename T constexpr ModInt(T x_) { T v x_ % M; if (v 0) { v M; } x v; } constexpr int val() const { return x; } constexpr ModInt operator() { x; if (x M) { x 0; } return *this; } constexpr ModInt operator(int) { ModInt res *this; (*this); return res; } constexpr ModInt operator--() { if (x 0) { x M - 1; } else { x--; } return *this; } constexpr ModInt operator--(int) { ModInt res *this; --(*this); return res; } constexpr ModInt operator-() const { ModInt res; res.x (x 0 ? 0 : M - x); return res; } constexpr ModInt inv() const { return power(*this, M - 2); } constexpr ModInt operator*(const ModInt rhs) { x ll(x) * rhs.val() % M; return *this; } constexpr ModInt operator(const ModInt rhs) { x rhs.val(); if (x M) { x - M; } return *this; } constexpr ModInt operator-(const ModInt rhs) { x - rhs.val(); if (x 0) { x M; } return *this; } constexpr ModInt operator/(const ModInt rhs) { return *this * rhs.inv(); } friend constexpr ModInt operator*(ModInt lhs, const ModInt rhs) { lhs * rhs; return lhs; } friend constexpr ModInt operator(ModInt lhs, const ModInt rhs) { lhs rhs; return lhs; } friend constexpr ModInt operator-(ModInt lhs, const ModInt rhs) { lhs - rhs; return lhs; } friend constexpr ModInt operator/(ModInt lhs, const ModInt rhs) { lhs / rhs; return lhs; } friend constexpr bool operator(ModInt lhs, const ModInt rhs) { return lhs.val() rhs.val(); } friend constexpr bool operator(ModInt lhs, const ModInt rhs) { return lhs.val() rhs.val(); } friend constexpr bool operator(ModInt lhs, const ModInt rhs) { return lhs.val() rhs.val(); } friend constexpr bool operator(ModInt lhs, const ModInt rhs) { return lhs.val() rhs.val(); } friend constexpr bool operator(ModInt lhs, const ModInt rhs) { return lhs.val() rhs.val(); } friend constexpr bool operator!(ModInt lhs, const ModInt rhs) { return lhs.val() ! rhs.val(); } friend constexpr std::istream operator(std::istream is, ModInt a) { ll i; is i; a i; return is; } friend constexpr std::ostream operator(std::ostream os, const ModInt a) { return os a.val(); } private: int x; }; templateint M, typename T ModIntM struct Comb { vectorT fac; vectorT inv; Comb(int n) { fac.assign(n, 1); for(int i1;in;i) { fac[i]fac[i-1]*i; } inv.assign(n, 1); inv[n-1]fac[n-1].inv(); for(int in-2;i0;i--) { inv[i]inv[i1]*(i1); } } templatestd::signed_integral U T P(U n, U m) { if(nm) { return 0; } return fac[n] * inv[n - m]; } templatestd::signed_integral U T C(U n, U m) { if(nm||m0) { return 0; } return fac[n] * inv[n - m] * inv[m]; } }; //power函数切记强转成 Z !!!!! constexpr int M 1e97; using Z ModIntM; constexpr int N 2e55; CombMcomb(N); templatestd::signed_integral U Z P(U n, U m) { return comb.P(n, m); } templatestd::signed_integral U Z C(U n, U m) { return comb.C(n, m); } void solve() { int n; cinn; vectorinta(n1); for(int i1;in;i) { cina[i]; } vectorZf(n1); for(int i1;in;i) { f[i]f[i-1](Z)1/i; } Z ans0; for(int i1;in;i) { ans(f[i]-1f[n-i1]-11)*a[i]; } ans*comb.fac[n]; coutansendl; } void init() { } signed main() { ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); int t1; //cint; init(); while(t--) { solve(); } return 0; }首先对于某一种删除顺序考虑建立下标为二叉搜索树删除时间为小根堆的笛卡尔树注意这里不以高度为小根堆了。那么建完可以发现对于每个位置其贡献的次数就是其在树上的高度因为只有当其祖先节点删除时才会贡献一次。那么对于所有 n! 种删除方案的总贡献如果能求出每种方案的期望贡献乘一下就是答案了。首先对于当前节点 u其高度期望就是其祖先节点数的期望再加一。那么又因为期望就是所有概率的累加那么 u 的期望就是其他 n-1 个点是 u 祖先的概率的累加。那么若要求 x 是 u 的祖先那么首先就要满足 x 在 u 之前删除。在这个基础上由于下标满足二叉搜索树的性质那么就要求 x 到 u 之间的所有节点都在 x 之后删除因为如果中间有个节点更早那么 x 和 u 就会被分开。那么如果要求 x 在 u 前面删除的概率首先总概率就是任意排列。但此时为了方便解决考虑不用 n! 处理。那么首先 x 到 u 这 x-u1 个数随意排列的方案数是 (x-u1)!之后再乘上其他数随意排列往里插的方案数。之后对于分子那么如果要让 x 在 u 前面就是在固定 x 之后剩下 x-u 个数在后面随意排列方案数就是 (x-u)!再乘上其他书随意排列往里插的方案数。此时就可以发现分子分母的后者可以消掉前者也可以消掉所以概率就是。那么预处理一下分数的累加和每次 O(1) 计算即可。这个转化成期望计算也太逆天了……总结努力努力再努力END

相关新闻

可信数据空间——可信数据空间AI大模型服务平台建设方案

可信数据空间——可信数据空间AI大模型服务平台建设方案

本文档为《可信数据空间 AI 大模型服务平台建设方案》,适配 xx 省 13 个试点政务部门(纪委监委、法院、卫健、应急等)、政务 IT 团队、AI 技术服务商及运维机构。核心围绕政务领域可信数据空间 AI 平台建设,以打破数据壁垒、提升政务智能化为目标,分三期(2025 - 2027 年)…

2026/10/18 15:00:16 阅读更多 →
Agent Harness的11大核心组件与工程实践(附下载)

Agent Harness的11大核心组件与工程实践(附下载)

在大模型智能体(Agent)的落地实践中,很多开发者都会遇到一个共性问题:简单演示场景下,依靠ReAct循环、基础工具和优质系统提示词,Agent可以稳定运行、效果出色。但一旦面对需要十余步操作的复杂任务&#x…

2026/10/19 17:07:07 阅读更多 →
【PTrade】为什么回测能赚钱,实盘却无法下单?7个排查步骤与诊断代码

【PTrade】为什么回测能赚钱,实盘却无法下单?7个排查步骤与诊断代码

本方案由 EasyQuant AI量化助手 提供。 问题背景 很多策略在 PTrade 回测中能正常产生收益,切换到实盘后却没有任何委托,或者日志显示下单了但始终没有成交。 这通常不代表策略逻辑一定错误,而是回测与实盘在交易时间、账户资金、涨跌停、停…

2026/10/18 7:49:36 阅读更多 →

最新新闻

提示词工程进阶路线:从结构化设计到大师级工程化管理

提示词工程进阶路线:从结构化设计到大师级工程化管理

讲个真实场景:你是不是也收藏过十几个“提示词合集”,里面贴着上百条据说“封神”的模板,真到要用的时候翻来翻去却找不到一条能直接改来用的?或者更尴尬一些——你照着模板改了几个字,结果AI给出的答案驴唇不对马嘴&a…

2026/10/19 17:07:28 阅读更多 →
LangGraph生产级State设计:序列化、重放与审计的实战指南

LangGraph生产级State设计:序列化、重放与审计的实战指南

先讲一个我实际撞上的事故。某客服自动回复系统上线跑了一个多月,开发阶段一直用的内存版 checkpoint,大家都觉得这图跑得挺稳。结果一换数据库版 checkpointer,图走到第三个节点直接抛异常——原因让人哭笑不得:上游同学在 State…

2026/10/19 17:07:28 阅读更多 →
任务型智能助手实战:多思AI的架构设计与调优全记录

任务型智能助手实战:多思AI的架构设计与调优全记录

如果你和我一样,最近半年被各种聊天助手搞得又惊又喜,应该能理解我的真实感受:能陪你闲聊的模型越来越多,但真要让它们帮你把一件事办成,比如整理一份会议纪要、规划一条合理的行程、在内部文档里快速找到答案&#xf…

2026/10/19 17:07:27 阅读更多 →
毕业论文AI辅助写作全指南:从选题到答辩的十个可落地方案

毕业论文AI辅助写作全指南:从选题到答辩的十个可落地方案

2025届的毕业论文,现在开题了吗?如果已经开始,你大概率在某个AI对话框前纠结过:直接让它写,怕学术不端;完全不用,又觉得周围的同学都在用,自己效率上吃亏。我的建议是——别把这个问…

2026/10/19 17:07:27 阅读更多 →
SpringCloud Gateway堆外内存溢出排查:Netty直接内存泄漏定位与调优

SpringCloud Gateway堆外内存溢出排查:Netty直接内存泄漏定位与调优

1. 从一次线上告警说起:堆外内存溢出到底有多隐蔽那天凌晨两点多,监控面板突然弹出一连串红色告警,网关节点在十分钟内连续重启了三次。登录机器翻看日志,满屏都是java.lang.OutOfDirectMemoryError: failed to allocate 16777216…

2026/10/19 17:07:27 阅读更多 →
RAG知识库文档处理实操:解析、切分与检索效果全面调优

RAG知识库文档处理实操:解析、切分与检索效果全面调优

“收藏备用!RAG知识库文档处理实操”,这个标题我第一眼看到就想点头。做RAG知识库的人,谁没被文档处理折磨过?模型选型、Prompt优化这些话题都是热热闹闹的技术高地,结果真正把项目拖死的,往往是最朴素的一…

2026/10/19 17:06:26 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →