LeetCode 2790. 长度递增组的最大数目

文章详细介绍了如何使用Splay树进行暴力模拟,解决题目中关于找到数组中长度递增组的最大数量问题。通过每次修改前k大的数,利用Splay树的特性高效地进行查找和修改,避免了平衡树或堆带来的高时间复杂度。文章提供了一种优化的解决方案,时间复杂度为O(nlogn),并给出了具体的代码实现。
摘要由CSDN通过智能技术生成

题目链接:长度递增组的最大数目

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;
	}
};

                

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IN0vation

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值