【ybtoj高效进阶 21170】投篮训练(贪心)(线段树)(构造)

投篮训练

题目链接:ybtoj高效进阶 21170

题目大意

给你一棵树,i 的父亲是 i/k 下取整。
然后要一个点的权值大于等于它子树内每个点的权值。
然后给你 n 个数,要你分给 n 个点作为权值。
然后要你在合法的情况下,使得分出来的序列字典序最大。

思路

首先我们考虑进行一个贪心。

从小到大枚举儿子,然后从后往前找位置,然后就直接填上。

但是其实会有问题,就比如 1,1,1,2k=2 的时候,你贪心得到的是 1,1,1,2,然后其实是可以 1,1,2,1
这是为什么呢,是因为有重复数字。

你可能会有重复数组,所以每次你找到那个数,你都要靠到最右的位置。
然后你左边的就要留那么多个位置给它,就都加上它的大小。
(然后这个加在枚举到第一个它的儿子的时候取消)

然后你每次放的时候就不是直接看了,你要找到第一个位置,它后面可以放的个数(就是它后面的长度减去你前面的数组)大于它的大小。
那不难想到这个数组可以用线段树,来支持修改和查询。

然后就好惹。

代码

#include<cmath> #include<cstdio> #include<vector> #include<iostream> #include<algorithm> using namespace std; int n, d[500001], sz[500001]; int ans[500001], fa[500001]; int lst[500001]; vector <int> e[500001]; bool out[500001]; double k; bool cmp(int x, int y) { return x > y; } void dfs(int now) { sz[now] = 1; for (int i = 0; i < e[now].size(); i++) { dfs(e[now][i]); sz[now] += sz[e[now][i]]; } } void dfs1(int now, int r) { for (int i = 0; i < e[now].size(); i++) { int to = e[now][i]; ans[to] = r - sz[to] + 1; dfs1(to, r); r -= sz[to]; } } struct XD_tree {//线段树 int a[500001 << 2], lzy[500001 << 2]; void up(int now) { a[now] = min(a[now << 1], a[now << 1 | 1]); } void down(int now) { if (!lzy[now]) return ; a[now << 1] += lzy[now]; a[now << 1 | 1] += lzy[now]; lzy[now << 1] += lzy[now]; lzy[now << 1 | 1] += lzy[now]; lzy[now] = 0; } void build(int now, int l, int r) { if (l == r) { a[now] = l; return ; } int mid = (l + r) >> 1; build(now << 1, l, mid); build(now << 1 | 1, mid + 1, r); up(now); } void insert(int now, int l, int r, int L, int R, int va) { if (L <= l && r <= R) { a[now] += va; lzy[now] += va; return ; } down(now); int mid = (l + r) >> 1; if (L <= mid) insert(now << 1, l, mid, L, R, va); if (mid < R) insert(now << 1 | 1, mid + 1, r, L, R, va); up(now); } int query(int now, int l, int r, int lim) { if (l == r) { if (a[now] < lim) return l + 1; return l; } down(now); int mid = (l + r) >> 1; if (a[now << 1 | 1] >= lim) return query(now << 1, l, mid, lim); else return query(now << 1 | 1, mid + 1, r, lim); } }T; int main() { // freopen("shoot.in", "r", stdin); // freopen("shoot.out", "w", stdout); scanf("%d %lf", &n, &k); for (int i = 1; i <= n; i++) scanf("%d", &d[i]); sort(d + 1, d + n + 1, cmp); lst[n] = 0; for (int i = n - 1; i >= 1; i--) if (d[i] != d[i + 1]) lst[i] = 0; else lst[i] = lst[i + 1] + 1; for (int i = 1; i <= n; i++) { int bef = floor(1.0 * i / k); e[bef].push_back(i); fa[i] = bef; } dfs(0); T.build(1, 1, n); for (int i = 1; i <= n; i++) { if (fa[i] && !out[fa[i]]) {//取消父亲标记 T.insert(1, 1, n, ans[fa[i]], n, sz[fa[i]] - 1); out[fa[i]] = 1; } int pl = T.query(1, 1, n, sz[i]); pl += lst[pl]; lst[pl]++;//找到位置并修改 pl -= lst[pl] - 1; ans[i] = pl; T.insert(1, 1, n, ans[i], n, -sz[i]);//标记 } for (int i = 1; i <= n; i++) printf("%d ", d[ans[i]]); return 0; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/YBTOJ_GXJJ_21170.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(25)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示