Codeforces.1051G.Distinctification(线段树合并 并查集)

题目链接


\(Description\)

给定\(n\)个数对\(A_i,B_i\)。你可以进行任意次以下两种操作:

  1. 选择一个位置\(i\),令\(A_i=A_i+1\),花费\(B_i\)。必须存在一个位置\(j\),满足\(A_i=A_j,\ i\neq j\),才可以进行。
  2. 选择一个位置\(i\),令\(A_i=A_i-1\),花费\(-B_i\)。必须存在一个位置\(j\),满足\(A_i=A_j+1\),才可以进行。
    你需要对于所有\(i\in[1,n]\),求使得\(A_1,A_2,...,A_i\)两两不同的最小花费是多少。

\(n,A_i\leq2\times10^5,\ 1\leq B_i\leq n且互不相同\)。

\(Solution\)

考虑如果\(A_i\)互不相同,若存在\(A_i+1=A_j\),则可以交换\(A_i,A_j\),花费为\(B_i-B_j\)。所以最后序列会被分成\(A_i\)连续的若干段,且每段\(B_i\)递减。
如果\(A_i\)有可能相同,可以先把\(A_i\)变成互不相同(把相同的位置上的数移到该连续段的右端点\(+1\)处,并查集维护),再进行同样的操作。

设数\(A_i\)最终变成了\(A_i'\)(按\(B_i\)排序后被放到了\(A_i'\)位置去),我们发现\(i\)的贡献就是\((A_i'-A_i)\times B_i\),也就是所有数的贡献是\(\sum_i A_i'\times B_i-\sum_i A_i\times B_i\)。所以我们只需要在按\(B_i\)排序的过程中维护每个元素新的位置及贡献(其实维护位置就是维护在连续段中的排名,直接插入即可)。
具体就是,对于一个连续段,以\(B_i\)为下标建线段树。最后\(B_i\)是递减的,所以每个区间的贡献就是,\(左区间的\sum B_i\times 右区间的元素个数\)(当然这只是段内相对排名改变的贡献,连续段整体还有 \(连续段左端点\times\sum B_i\)的贡献)。

加入一个\(A_i\)时,可能把两个连续段合并在一起。把原先两个连续段的贡献减掉,然后线段树合并两段,再加上合并后的段的贡献即可。

复杂度\(O(n\log n)\)(为啥\(Tutorial\)上写的是\(O(n\log^2n)\)...?没细看...)。

注意数组大小要开\(4e5\)!不是\(2e5\)!(值域会扩大)
当你连WA 8遍且发现输出都不一样的时候,基本就是RE了= = 气死我了


//499ms 161100KB
#include <cstdio>
#include <cctype>
#include <algorithm>
#define gc() getchar()
typedef long long LL;
const int N=4e5+3;//4e5 not 2e5!

int fa[N],R[N],root[N];
LL Ans;
struct Segment_Tree
{
    #define ls son[x][0]
    #define rs son[x][1]
    #define lson ls,l,m
    #define rson rs,m+1,r
    #define S N*20
    int tot,sz[S],son[S][2];
    LL sum[S];
    #undef S
    #define Update(x) sz[x]=sz[ls]+sz[rs], sum[x]=sum[ls]+sum[rs]
    void Insert(int &x,int l,int r,int pos)
    {
        if(!x) x=++tot;
        if(l==r) {sz[x]=1, sum[x]=pos; return;}
        int m=l+r>>1;
        pos<=m ? Insert(lson,pos) : Insert(rson,pos);
        Update(x);
    }
    int Merge(int x,int y)
    {
        if(!x||!y) return x|y;
        Ans-=sum[ls]*sz[rs]+sum[son[y][0]]*sz[son[y][1]];
        ls=Merge(ls,son[y][0]), rs=Merge(rs,son[y][1]);
        Ans+=sum[ls]*sz[rs], Update(x);
        return x;
    }
}T;

inline int read()
{
    int now=0;register char c=gc();
    for(;!isdigit(c);c=gc());
    for(;isdigit(c);now=now*10+c-48,c=gc());
    return now;
}
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.sum[root[x]]*x+T.sum[root[y]]*y;
    root[x]=T.Merge(root[x],root[y]);
    Ans+=T.sum[root[x]]*x;
    R[x]=R[y];
}

int main()
{
    const int n=read();
    for(int i=1; i<N; ++i) R[i]=i, fa[i]=i;
    for(int i=1; i<=n; ++i)
    {
        int a=read(),b=read(),p=root[a]?R[Find(a)]+1:a;
        Ans-=1ll*a*b, T.Insert(root[p],1,n,b), Ans+=1ll*p*b;
        if(root[p-1]) Merge(p-1,p);
        if(root[p+1]) Merge(p,p+1);
        printf("%I64d\n",Ans);
    }

    return 0;
}

原文地址:https://www.cnblogs.com/SovietPower/p/10374029.html

时间: 2024-08-01 12:21:18

Codeforces.1051G.Distinctification(线段树合并 并查集)的相关文章

Codeforces 1140F 线段树 分治 并查集

题意及思路:https://blog.csdn.net/u013534123/article/details/89010251 之前cf有一个和这个相似的题,不过那个题只有合并操作,没有删除操作,直接并查集搞一搞就行了.对于这个题,因为有删除操作,我们对操作序列建一颗线段树,记录每个操作影响的区间操作就可以了.这里的并查集不能路径压缩,要按秩合并,这样复杂度是O(logn)的. 代码: #include <bits/stdc++.h> #define ls (o << 1) #de

线段树分治总结(线段树分治,线段树,并查集,树的dfn序,二分图染色)

闲话 stO猫锟学长,满脑子神仙DS 线段树分治思想 我们在做CDQ的时候,将询问和操作通通视为元素,在归并过程中统计左边的操作对右边的询问的贡献. 而在线段树分治中,询问被固定了.按时间轴确定好询问的序列以后,我们还需要所有的操作都会影响一个时间区间.而这个区间,毫无疑问正好对应着询问的一段区间. 于是,我们可以将每一个操作丢到若干询问里做区间修改了,而线段树可以高效地维护.我们开一个叶子节点下标为询问排列的线段树,作为分治过程的底层结构. 具体的实现,仍然要看题目. 例题1 BZOJ4025

[BZOJ4025]二分图(线段树分治,并查集)

4025: 二分图 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 2191  Solved: 800[Submit][Status][Discuss] Description 神犇有一个n个节点的图.因为神犇是神犇,所以在T时间内一些边会出现后消失.神犇要求出每一时间段内这个图是否是二分图.这么简单的问题神犇当然会做了,于是他想考考你. Input 输入数据的第一行是三个整数n,m,T. 第2行到第m+1行,每行4个整数u,v,start,end

Codeforces Gym 101194G Pandaria (2016 ACM-ICPC EC-Final G题, 并查集 + 线段树合并)

题目链接  2016 ACM-ICPC EC-Final Problem G 题意  给定一个无向图.每个点有一种颜色. 现在给定$q$个询问,每次询问$x$和$w$,求所有能通过边权值不超过w的边走到$x$的点的集合中,哪一种颜色的点出现的次数最多. 次数相同时输出编号最小的那个颜色.强制在线. 求哪种颜色可以用线段树合并搞定. 关键是这个强制在线. 当每次询问的时候,我们先要求出最小生成树在哪个时刻恰好把边权值不超过$w$的边都用并查集合并了. 在做最小生成树的时候每合并两个节点,另外开一个

Codeforces 1140F Extending Set of Points 线段树 + 按秩合并并查集 (看题解)

Extending Set of Points 我们能发现, 如果把x轴y轴看成点, 那么答案就是在各个连通块里面的x轴的个数乘以y轴的个数之和. 然后就变成了一个并查集的问题, 但是这个题目里面有撤销的操作, 所以我们要把加入和撤销操作变成 这个点影响(L , R)之间的询问, 然后把它丢到线段树里面分成log段, 然后我们dfs一遍线段树, 用按秩合并并查集取维护, 回溯的时候将并查集撤销. #include<bits/stdc++.h> #define LL long long #def

CodeForces 600E Lomsat gelral(线段树合并)

题目链接:http://codeforces.com/problemset/problem/600/E You are given a rooted tree with root in vertex 1. Each vertex is coloured in some colour. Let's call colour c dominating in the subtree of vertex v if there are no other colours that appear in the

Educational Codeforces Round 47 (Rated for Div. 2)F. Dominant Indices 线段树合并

题意:有一棵树,对于每个点求子树中离他深度最多的深度是多少, 题解:线段树合并快如闪电,每个节点开一个权值线段树,递归时合并即可,然后维护区间最多的是哪个权值,到x的深度就是到根的深度减去x到根的深度复杂度O(nlogn) //#pragma comment(linker, "/stack:200000000") //#pragma GCC optimize("Ofast,no-stack-protector") //#pragma GCC target("

Codeforces ECR47F Dominant Indices(线段树合并)

一个比较显然的做法:对每棵子树用线段树维护其中的深度,线段树合并即可. 本来想用这个题学一下dsu on tree,结果还是弃疗了. #include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> #include<cstring> #include<algorithm> using namespace std; int read() { int x=0,f=

codeforces 600E . Lomsat gelral (线段树合并)

You are given a rooted tree with root in vertex 1. Each vertex is coloured in some colour. Let's call colour c dominating in the subtree of vertex v if there are no other colours that appear in the subtree of vertex v more times than colour c. So it'