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


    题意

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

    • 若存在 (j ot = i) 满足 (a_j = a_i) , 则可以花费 (b_i) 的代价令 (a_i) 加一 。
    • 若存在 (j) 满足 (a_j + 1 = a_i) , 则可以花费 (−b_i) 的代价令 (a_i) 减一 。

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

    (n, a_i le 2 imes 10^5, 1 le b_i le n)

    题解

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

    考虑所有 (a_i) 互不相同的时候怎么做 , 若存在 (a_i + 1 = a_j) , 则可以花费 (b_i − b_j) 的代价交换两个 (a_i)

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

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

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

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

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

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

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

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

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

    代码

    #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
    ", ans);
    	}
    
        return 0;
    
    }
    
  • 相关阅读:
    9.3 simulated match
    网络流模版大全
    Treblecross
    ENimEN
    求逆序对的两种方法(树状数组/归并排序)
    树状数组
    计算最短路和次短路条数
    Python3.7版库的安装以及常用方法(十分简单)
    二维线段树(hdu1823)
    流星雨(记忆化搜索)
  • 原文地址:https://www.cnblogs.com/zjp-shadow/p/9752663.html
Copyright © 2020-2023  润新知