题目链接:长度递增组的最大数目
PS:这道题最优的方法应该是贪心的思想;暴力模拟需要用到平衡树,用堆的话时间复杂度太大,考虑到自己好久没有写过Splay树了,就拿这道题练了下手。
题解:Splay树暴力模拟
1. 每次修改前k大的数直到无法修改
2. 每个结点需要维护的信息:
2. 每次操作,找到第k大的元素结点x,将其旋转到根节点:因为x的左子树全部比它小,x的右子树全部比它大,所以我们需要修改的就是整个右子树和x的值
3. 如果x的权值 <=0 则模拟结束
4. 右子树的修改:将x右孩子结点的懒标记-1
5. x结点的修改,因为可能存在相同元素,可能面临“需要x的部分元素权值-1,而余下部分权值不变”的情况,维护策略如下:
b. 先将x的左右子树合并得到新的Splay树(因为x右子树的元素比x左子树的元素至少大2,如今把右子树的元素全部减-1,依旧大于左子树元素,保持搜索性质),x从树中删除
c. 假设x有cnt个相同的元素,除去右子树外还需要修改xCnt个元素:将x分裂为两个结点,元素个数分别为cnt-xCnt(权值不变)和xCnt(权值-1),再将这个两个结点插回树中
建树需要n次插入;至多n次操作,每次操作至少搜索一次,至多插入两次
总的时间复杂度为O(nlogn)
代码示例:
class Solution {
public:
#define N 100010
int root = 0, idx = 0;
struct Node {
// 左右函数下标、父节点下标、权值、懒标记、子树元素总数、相同权值元素个数
int ch[2], fa, val, lazy, sum, cnt;
// 初始化方法
void Init(int fa, int val, int cnt) {
ch[0] = ch[1] = lazy = 0;
sum = this->cnt = cnt;
this->fa = fa;
this->val = val;
}
// 清理函数
inline void Clear() {
fill_n((char*)this, sizeof(Node), 0);
}
// 移动赋值
Node& operator =(Node&& v)noexcept {
copy_n((char*)&v, sizeof(Node), (char*)this);
v.Clear();
return *this;
}
}tr[N];
// 用孩子结点信息维护x结点信息
inline void PushUp(int x) {
tr[x].sum = tr[x].cnt + tr[tr[x].ch[0]].sum + tr[tr[x].ch[1]].sum;
}
// 懒标记下移
inline void PushDown(int x) {
if (tr[x].lazy) {
if (tr[x].ch[0]) {
tr[tr[x].ch[0]].val += tr[x].lazy;
tr[tr[x].ch[0]].lazy += tr[x].lazy;
}
if (tr[x].ch[1]) {
tr[tr[x].ch[1]].val += tr[x].lazy;
tr[tr[x].ch[1]].lazy += tr[x].lazy;
}
tr[x].lazy = 0;
}
}
// 旋转函数
void Rotate(int x) {
int y = tr[x].fa, z = tr[y].fa;
bool chk = x == tr[y].ch[1];
tr[y].fa = x;
tr[y].ch[chk] = tr[x].ch[!chk];
if (tr[x].ch[!chk]) tr[tr[x].ch[!chk]].fa = y;
tr[x].fa = z;
tr[x].ch[!chk] = y;
if (z) tr[z].ch[y == tr[z].ch[1]] = x;
PushUp(y);
PushUp(x);
}
// 核心函数:将x结点旋转到fa结点下面(fa为0表示旋转为根节点)
void Splay(int x, int fa = 0) {
int y, z;
while (tr[x].fa != fa) {
y = tr[x].fa;
z = tr[y].fa;
if (z != fa) {
if (tr[y].ch[1] == x && tr[z].ch[1] == y) Rotate(y);
else Rotate(x);
}
Rotate(x);
}
if (!fa) root = x;
}
// 插入函数
void Insert(int val, int cnt = 1) {
if (!root) {
tr[root = ++idx].Init(0, val, cnt);
return;
}
int u = root, fa = 0;
while (u) {
if (tr[u].lazy) PushDown(u);
if (tr[u].val == val) break;
fa = u;
u = tr[u].ch[val > tr[u].val];
}
if (!u) {
tr[u = ++idx].Init(fa, val, cnt);
tr[fa].ch[val > tr[fa].val] = u;
}
else tr[u].cnt += cnt;
PushUp(u);
PushUp(fa);
Splay(u);
}
// 找第k小
int FindByRank(int k) {
if (!root || k < 1 || k > tr[root].sum) return -1;
int u = root, tmp;
while (k) {
if (tr[u].lazy) PushDown(u);
tmp = tr[tr[u].ch[0]].sum;
if (tmp >= k) u = tr[u].ch[0];
else if (tmp + tr[u].cnt >= k) break;
else {
k -= tmp + tr[u].cnt;
u = tr[u].ch[1];
}
}
if (tr[u].lazy) PushDown(u);
Splay(u);
return u;
}
// 合并两子树
int Combine(int x, int y) {
if (!x) return root = y;
if (!y) return root = x;
int u = x;
if (tr[u].lazy) PushDown(u);
while (tr[u].ch[1]) {
u = tr[u].ch[1];
if (tr[u].lazy) PushDown(u);
}
Splay(u);
tr[u].ch[1] = y;
tr[y].fa = u;
PushUp(u);
return root = u;
}
// 修改前k大
bool ModifyK(int k) {
if (!root || tr[root].sum < k) return false;
int x = FindByRank(tr[root].sum - k + 1);
if (x < 0 || tr[x].val <= 0) return false;
int xCnt = k - tr[tr[x].ch[1]].sum;
int l = tr[x].ch[0], r = tr[x].ch[1];
tr[l].fa = 0;
tr[r].fa = 0;
if (r) {
--tr[r].lazy;
--tr[r].val;
}
root = Combine(l, r);
int cnt = tr[x].cnt, val = tr[x].val;
// 清理x结点,并回收资源:将下标最大的结点移动到x结点处
tr[x] = move(tr[idx]);
if (tr[x].ch[0]) tr[tr[x].ch[0]].fa = x;
if (tr[x].ch[1]) tr[tr[x].ch[1]].fa = x;
if (tr[x].fa) tr[tr[x].fa].ch[tr[tr[x].fa].ch[1] == idx] = x;
if (root == idx--) root = x;
if (val > 1) Insert(val - 1, xCnt);
if (xCnt < cnt) Insert(val, cnt - xCnt);
return true;
}
int maxIncreasingGroups(vector<int>& usageLimits) {
for (auto i : usageLimits) Insert(i);
int res = 1;
while (ModifyK(res)) ++res;
return res - 1;
}
};