G. Distinctification
分析:
线段树合并 + 并查集。
最后操作完后a连续递增的一段,b一定是递减的。最后的答案是$sum (a_{new}-a_{odd}) imes b_i$,即改变后的a减去之前的a。
那么对于连续的一段考虑怎么求。按照bi建立权值线段树,线段树的一个节点的答案就是 左区间的答案+右区间的答案+左区间的和 × 右区间的个数。
即最大的$b_i$乘1,次大的乘2,...,最小的乘(n-1)分别是每个$b_i$的排名。这是对b排序后,新的答案,减去以前的即可得到答案。
如果存在相同的a,那么让a变成a+1,存在a+1,那么变成a+2……加上变后的贡献,并查集维护。注意扩大值域后,开两倍空间。
代码:
#include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #include<iostream> #include<cctype> #include<set> #include<vector> #include<queue> #include<map> #define fi(s) freopen(s,"r",stdin); #define fo(s) freopen(s,"w",stdout); using namespace std; typedef long long LL; inline int read() { int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; } const int N = 400005; int ls[N * 20], rs[N * 20], siz[N * 20], Root[N], R[N], fa[N], Index; LL sum[N * 20], Ans; void Insert(int l,int r,int &now,int p) { if (!now) now = ++Index; if (l == r) { siz[now] = 1, sum[now] = p; return ; } int mid = (l + r) >> 1; if (p <= mid) Insert(l, mid, ls[now], p); else Insert(mid + 1, r, rs[now], p); sum[now] = sum[ls[now]] + sum[rs[now]], siz[now] = siz[ls[now]] + siz[rs[now]]; } int Merge(int x,int y) { if (!x || !y) return x | y; Ans -= sum[ls[x]] * siz[rs[x]] + sum[ls[y]] * siz[rs[y]]; ls[x] = Merge(ls[x], ls[y]); rs[x] = Merge(rs[x], rs[y]); Ans += sum[ls[x]] * siz[rs[x]], siz[x] += siz[y], sum[x] += sum[y]; return x; } int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); } void solve(int x,int y) { x = find(x), y = find(y); fa[y] = x; Ans -= sum[Root[x]] * x + sum[Root[y]] * y; Root[x] = Merge(Root[x], Root[y]); Ans += sum[Root[x]] * x; R[x] = R[y]; } int main() { int n = read(); for (int i = 1; i <= 400000; ++i) fa[i] = i, R[i] = i; for (int i = 1; i <= n; ++i) { int a = read(), b = read(); int p = Root[a] ? R[find(a)] + 1 : a; Ans -= 1ll * a * b; Insert(1, n, Root[p], b); Ans += 1ll * p * b; if (Root[p - 1]) solve(p - 1, p); if (Root[p + 1]) solve(p, p + 1); printf("%I64d ", Ans); } return 0; }