1080 字
5 分钟
竞赛算法(12) 杂项
mex
mex的定义: 最小未出现非负整数. 未出现这个性质可能非常有用. (有些题目中这个定义为最小未出现正整数, 全部-1即可).
我们可以扩展定义mkex: 用m2ex表示第2小的未出现非负整数. mkex表示第k小的未出现非负整数.
求法
mex有一个很经典的性质: 长度为n的数组A的mex一定小于n + 1, 读者自证不难.
同样地, 长度为n的mkex一定小于n + k.
所以要求mex, 只需要开一个长度为n + 1的cnt数组, 然后对于所有小于等于n的数字用cnt计数. 最后从0开始遍历到第一个cnt[i] == 0的位置即可.
ll getmex(const vector<ll>& arr) { int n = arr.size(); vector<int> cnt(n + 1); for (int i = 0; i < n; i++) { if (arr[i] < 0 || arr[i] > n) { continue; } cnt[arr[i]]++; } int mex = 0; while (cnt[mex] != 0) { mex++; } return mex;}求mkex
- 我们首先定义
misscnt(i)为小于等于i的缺失数. 则问题转化为找到使得misscnt(t) = k且不在数组中的数字t(t是唯一的). - 注意到对于排序去重去负数后的数组
arr来说,misscnt(arr[i]) = arr[i] - i, 这个差值是单调不减的. - 对于不在数组
arr中的数t来说, 我们找到最大的arr[idx] < t的下标idx.t夹在arr[idx]和arr[idx + 1]之间, 则misscnt(t) - misscnt(arr[idx]) = t - arr[idx]. - 这里还有一个trick, 找最大的
arr[idx] < t, 就是找最大的misscnt[arr[idx]] < misscnt(t), 因为(arr[idx], t]这个区间中所有数均不在数组中, 其misscnt必然严格递增(每次+1). - 由于
misscnt(t) = k, 则k - (arr[idx] - idx) = t - arr[idx]. - 答案即为
t = k + idx.
ll getmkex(vector<int> arr, ll k) { // 排序去重去负数 for (auto& x : arr) { if (x < 0) { x = -1; // 负数标记一下, 最后一起清除 } } // 去重 sort(arr.begin(), arr.end()); arr.erase(unique(arr.begin(), arr.end()), arr.end()); if (!arr.empty() && arr[0] == -1) { arr.erase(arr.begin()); }
// find maximum idx that arr[idx] - idx < k. int lt = 0, rt = (int)arr.size() - 1; int idx = -1; while (lt <= rt) { int mid = lt + (rt - lt) / 2; if (arr[mid] - mid < k) { lt = mid + 1; idx = mid; } else { rt = mid - 1; } } return (ll)k + idx;}动态mex(MexSet)
维护一个集合, 支持增加, 删除, 查询mex操作.
struct MexSet { HashMap<int, int> cnt; OrderedSet<int> miss; // 未出现的数字 // HashMap 和 OrderedSet 见数据结构pbds库部分. 不卡常的情况下可用 map 和 set 代替 int max_val; MexSet(int n) : max_val(n + 1) { for (int i = 0; i <= n; i++) { miss.insert(i); } }
void add(int x) { if (x > max_val) { return; }
cnt[x]++; if (cnt[x] == 1) { miss.erase(x); } }
void remove(int x) { if (x > max_val) { return; } cnt[x]--; if (cnt[x] == 0) { miss.insert(x); } }
int mex() { return *miss.begin(); }};区间mex
离线
使用莫队配合上面的MexSet即可.
在线
需要用到主席树, 待补充.
一些trick
- 构造题涉及mex的时候, 通常先考虑0, 1, 2是否存在. 2066B
med(中位数)
中位数的性质: 假设数组A长度为n. 某元素arr[i]是中位数当且仅当: arr[i]大于等于其中至少(n + 1) / 2个元素, 且小于等于其中至少(n + 1) / 2个元素.
要寻找一个最大中位数, 我们可以忽略条件2, 然后进行二分搜索.
而涉及至少/2上取整的问题的时候, 有一个比较简单的验证方法: 所有满足该性质的赋1, 所有不满足该性质的位置赋值-1. 做前缀和, 如果子段和大于等于0, 则我们称数组中至少有(n + 1) / 2的满足该性质的数.
例题: 2128E1
主定理(master公式)
用于求解分治算法的时间复杂度.
若
我们去比较与, 分为如下几种情况:
- 若, 则时间复杂度由递归主导,
- 若, 则时间复杂度由递归部分与非递归部分共同影响, . 需要注意不算阶数.
- 若, 同时满足正则条件(通常竞赛题目都满足), 则时间复杂度由非递归主导,
竞赛算法(12) 杂项
https://fuwari.vercel.app/posts/竞赛算法12-杂项/