zoukankan      html  css  js  c++  java
  • Codeforces.1051G.Distinctification(线段树合并 并查集)

    题目链接


    (Description)

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

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

    (n,A_ileq2 imes10^5, 1leq B_ileq 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) imes B_i),也就是所有数的贡献是(sum_i A_i' imes B_i-sum_i A_i imes B_i)。所以我们只需要在按(B_i)排序的过程中维护每个元素新的位置及贡献(其实维护位置就是维护在连续段中的排名,直接插入即可)。
    具体就是,对于一个连续段,以(B_i)为下标建线段树。最后(B_i)是递减的,所以每个区间的贡献就是,(左区间的sum B_i imes 右区间的元素个数)(当然这只是段内相对排名改变的贡献,连续段整体还有 (连续段左端点 imessum B_i)的贡献)。

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

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

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

    ps:似乎不用维护左端点,有右端点有(size)不就有左端点了吗。


    //483ms	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], sz[x]+=sz[y], sum[x]+=sum[y];// Update(x); 不要写Update...(虽然这题能过)
    		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
    ",Ans);
    	}
    
    	return 0;
    }
    
  • 相关阅读:
    kotlin类与对象——>对象表达式与对象声明、内联类
    kotlin类与对象——>嵌套类与内部类、枚举类
    kotlin类与对象——>数据类、密封类、泛型
    UIView的setNeedsLayout, layoutIfNeeded 和 layoutSubviews 方法之间的关系解释
    xcode 统计代码行数
    iOS 常用的一些公用方法
    如何在一个项目中去建立多个Target
    instrument linker 的使用
    25 优化技巧
    二维码生成与扫描
  • 原文地址:https://www.cnblogs.com/SovietPower/p/10374029.html
Copyright © 2011-2022 走看看