带权中位数
带权中位数 :给定数组 W,X,保证 Wi≥0。求 kargmini=1∑NWi∣k−Xi∣。
先考虑不带权的,那么带权就是 Xi 出现了 Wi 次。按 Xi 升序排序,顺序累加 Wi,找到第一个使得 Wi 的前缀和 ≥21i=1∑NWi 的位置 m,则最优解为 k∗=Xm。
例 1:N 个人围成一圈,第 i 个人初始有 Ai 个物品。i 与 (i+1)modN 每传递 1 个物品的代价为 Bi。欲通过若干次传递使每个人手中的物品数量相同,求最小总代价。
设 di=Ai−Aˉ。设 i 传递给 (i+1)modN 了 ci 个(可能为负)。根据物品守恒,ci=cN+Di。则总传递代价 Ans(cN)=i=1∑NBi∣ci∣=i=1∑NBi∣cN+Di∣。欲构造 cN 以最小化 Ans(cN),这就是带权中位数问题。
例 2:序列 A,B,设有理数序列 c,d 满足 (Ai+ci)(Bj+dj)=(Aj+cj)(Bi+di),最小化 ∑∣ci∣+∣di∣。(ABC479G)
要么 ci≡0,要么 di≡0。以 ci≡0 为例,设 Bi+di=kAi,需最小化 ∑∣di∣=∑∣kAi−Bi∣=∑∣Ai∣k−AiBi,这就是带权中位数问题。注意特判非零的 A 为空。
将 (Ai,Bi) 看作二维坐标系的点,将每个点移动到同一条过原点的直线,代价是 ∣ci∣+∣di∣。当 ∣k∣⩽1 时,点纵向移动不劣,∣k∣>1 时,横向不劣。
排序不等式
排序不等式 :只改变配对方式,
- ∑AiBπ(i):同序最大,逆序最小
- ∑∣Ai−Bπ(i)∣:同序最小,逆序最大
- ∑(Ai−Bπ(i))2:同序最小,逆序最大
例 :序列 A,B,其中 ∣A∣⩽∣B∣,选取 B 的任一 ∣A∣ 元子列的任一排列 b,最大化 ∑∣Ai−bi∣。(CF1921D)
不妨设 A 递增。由排序不等式,b 递减,将 B 倒序排序,再选取一个递减子列 b。而且 b 是 B 的某前缀与某后缀拼接起来的。枚举分界点计算答案。O(NlogN)。
区间包含树
区间包含树 :给定若干区间 [Li,Ri),要么互不相交(Ri≤Lj),要么一个包含另一个(Li≤Lj<Rj≤Ri),不存在交叉重叠(Li<Lj<Ri<Rj),构造区间包含树。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| vector<vector<int>> build(const vector<pair<int, int>>& seg) { const int N = int(seg.size()); struct Seg { int l, r, id; }; vector<Seg> A(N + 1); constexpr int inf = 0x3f3f3f3f; A[0] = {-inf, inf, 0}; for (int i = 0; i < N; i++) { auto [l, r] = seg[i]; A[i + 1] = {l, r, i + 1}; } ranges::sort(A, {}, [](const Seg& x) { return pair(x.l, -x.r); }); vector<int> s; vector<vector<int>> adj(N + 1); for (int i = 0; i <= N; i++) { while (!s.empty() && A[s.back()].r <= A[i].l) { s.pop_back(); } if (!s.empty()) { adj[A[s.back()].id].push_back(A[i].id); } s.push_back(i); } return adj; }
|
异或线性基
求子集的数量,满足异或和为 0。答案为 2N−rank。
正则括号串
正则括号串 (Regular Bracket Sequence, RBS):
- 生成方式:连接与加括号
- 前缀和保持非负
- 一一对应于有序树 T=(T1T2…Tk)
- 一一对应于有序二叉树 T=(TL)TR
——CuteCube
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| vector<vector<int>> build(string S) { S = "(" + S + ")"; vector<vector<int>> adj; stack<int> s; for (auto c : S) { if (c == '(') { int x = int(adj.size()); if (!s.empty()) { adj[s.top()].push_back(x); } s.push(x); adj.emplace_back(); } else { s.pop(); } } return adj; }
string restore(const vector<vector<int>>& adj) { string S; auto dfs = [&](this auto&& self, int x) -> void { S += '('; for (auto y : adj[x]) { self(y); } S += ')'; }; for (auto y : adj[0]) { dfs(y); } return S; }
|
例 1:删除 BS S 至多 K 个字符,最小化最长 RBS 子序列。(CF2233C)
先做一遍括号匹配,最后希望剩下 )))((,右括号与左括号有个分界点。重点是绝对不存在一对匹配的括号,左括号在左半区,右在右半区,所以所有匹配的括号对要么全在左半,要么全在右半。左半的括号对删左,右半删右。如此最优。
例 2:RBS S,任意操作:选择两个不交的 RBS 子串,并交换位置。判断最终是否可以变换为 T。(CF2210D)
在树上做,操作等价于:选两个没有祖先关系的点 x,y,将 x 的若干子节点的子树与 y 的做交换。不变量:叶子节点数;从根到第一个分叉节点之间的节点数。