Educational Codeforces Round 51 (Rated for Div. 2) G. Distinctification(线段树合并 + 并查集)

题意

给出一个长度为 n 序列 , 每个位置有 ai,bi 两个参数 , bi 互不相同 ,你可以进行任意次如下的两种操作 :

  • 若存在 ji 满足 aj=ai , 则可以花费 bi 的代价令 ai 加一 。
  • 若存在 j 满足 aj+1=ai , 则可以花费 bi 的代价令 ai 减一 。

定义一个序列的权值为将序列中所有 ai 变得互不相同所需的最小代价 。 现在你要求出给定序列的每一个前缀的权值 。

n,ai2×105,1bin

题解

以下很多拷贝自 Wearry 题解(侵删) :

考虑所有 ai 互不相同的时候怎么做 , 若存在 ai+1=aj , 则可以花费 bibj 的代价交换两个 ai

显然最优方案会将序列中所有 ai 连续的子段操作成按 bi 降序的 。

然后如果有 ai 相同 , 则可以先将所有 ai 变成互不相同的再进行排序 , 但是这时可能会扩大值域使得原本不连续的两个区间合并到一起 , 于是我们需要维护一个支持合并的数据结构 。

我们用并查集维护每个值域连续的块 , 并在每个并查集的根上维护一个以 b 为关键字的值域线段树 , 每次合并两个联通块时 , 合并他们对应的线段树即可维护答案 。

说起来好像都听懂了,但是线段树以及并查集那里实现似乎不那么明了。

考虑对于每个并查集维护对于 ai 的一段连续的区间。如果当前的 a 已经出现过,那么我把当前的 a 扩展到当前的区间右端点 +1

我们发现答案是最后的 iaibi 减去初始的 iaibi ,因为我们对于每个 bi 考虑,它的贡献就是它的 a 的变化值。

然后考虑线段树上维护这个信息。合并的时候,不难发现就是左区间的 ibi 乘上右区间的元素个数,因为我们是考虑把 bi 降序排列的。

然后就比较好 实现了。。。

注意合并的时候需要定向到最右边。

代码

#include <bits/stdc++.h> #define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Set(a, v) memset(a, v, sizeof(a)) #define Cpy(a, b) memcpy(a, b, sizeof(a)) #define debug(x) cout << #x << ": " << (x) << endl #define DEBUG(...) fprintf(stderr, __VA_ARGS__) using namespace std; typedef long long ll; template<typename T> inline bool chkmin(T &a, T b) {return b < a ? a = b, 1 : 0;} template<typename T> inline bool chkmax(T &a, T b) {return b > a ? a = b, 1 : 0;} inline int read() { int x(0), sgn(1); char ch(getchar()); for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1; for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48); return x * sgn; } void File() { #ifdef zjp_shadow freopen ("G.in", "r", stdin); freopen ("G.out", "w", stdout); #endif } template<int Maxn> struct Segment_Tree { int ls[Maxn], rs[Maxn], tot[Maxn], Size; Segment_Tree() { Size = 0; } ll res[Maxn], val[Maxn]; inline void Push_Up(int o) { tot[o] = tot[ls[o]] + tot[rs[o]]; val[o] = val[ls[o]] + val[rs[o]]; res[o] = res[ls[o]] + res[rs[o]] + val[ls[o]] * tot[rs[o]]; } void Update(int &o, int l, int r, int up) { if (!o) o = ++ Size; if (l == r) { tot[o] = 1; val[o] = up; return ; } int mid = (l + r) >> 1; if (up <= mid) Update(ls[o], l, mid, up); else Update(rs[o], mid + 1, r, up); Push_Up(o); } int Merge(int x, int y, int l, int r) { if (!x || !y) return x | y; int mid = (l + r) >> 1; ls[x] = Merge(ls[x], ls[y], l, mid); rs[x] = Merge(rs[x], rs[y], mid + 1, r); Push_Up(x); return x; } }; const int N = 4e5 + 1e3; Segment_Tree<N * 20> T; int rt[N], fa[N], lb[N], rb[N], n; ll ans = 0; int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); } void Merge(int x, int y) { x = find(x); y = find(y); fa[y] = x; ans -= T.res[rt[x]] + 1ll * lb[x] * T.val[rt[x]]; ans -= T.res[rt[y]] + 1ll * lb[y] * T.val[rt[y]]; chkmin(lb[x], lb[y]); chkmax(rb[x], rb[y]); rt[x] = T.Merge(rt[x], rt[y], 1, n); ans += T.res[rt[x]] + 1ll * lb[x] * T.val[rt[x]]; } int main () { File(); n = read(); For (i, 1, N - 1e3) lb[i] = rb[i] = fa[i] = i; For (i, 1, n) { int a = read(), b = read(), t; ans -= 1ll * a * b; t = rt[find(a)] ? rb[find(a)] + 1 : a; T.Update(rt[t], 1, n, b); ans += T.res[rt[t]] + 1ll * t * T.val[rt[t]]; if (rt[find(t - 1)]) Merge(t, t - 1); if (rt[find(t + 1)]) Merge(t + 1, t); printf ("%lld\n", ans); } return 0; }

__EOF__

本文作者zjp_shadow
本文链接https://www.cnblogs.com/zjp-shadow/p/9752663.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zjp_shadow  阅读(344)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示