CF1051G (Edu Round 51) 题解

vp 这场 CF 之后写了篇总结博客,喊帆巨@zhiyangfan 验了一遍,帆巨锐评这道 G 是厉害题,于是我决定提出来它细化出篇题解。

G. Distinctification

对于含 \(k\) 个数对 \((a_i,b_i)\) 的序列 \(S\),可进行如下操作:

  • 任选一个 \(i\),当 \(\exists j\ne i,a_i=a_j\) 时,可令 \(a_i\gets a_i+1\),花费为 \(b_i\)

  • 任选一个 \(i\),当 \(\exists j,a_i=a_j+1\) 时,可令 \(a_i\gets a_i-1\),花费为 \(-b_i\)

定义 \(f(S)\) 为进行操作使得 \(S\)\(a_i\) 两两不同的最小花费。

给出含 \(n\) 个数对 \((a_i,b_i)\) 的序列 \(P\),保证 \(b_i\) 两两不同,对 \(i\in[1,n]\),求 \(f(P_{[1,i]})\)

\(1\le b_i\le n\le2\cdot10^5,1\le a_i\le2\cdot10^5\)

首先考虑如题意的操作所带来的性质。

  • 容易发现所给的两项操作是互逆的,即若在某位置进行了一项操作,立刻对该位置进行另一项操作是可行的,且操作后的序列和花费与两次操作前完全相同。

  • \(\exists i,j,a_i=a_j\) 时,由于题意限制,必须使用操作一,直至 \(a_i\) 两两不同。

  • \(\exists i,j,a_i=a_j+1\) 时,可以对 \(i\) 使用一次操作二、对 \(j\) 使用一次操作一,以 \(b_j-b_i\) 的花费交换 \(a_i,a_j\),也即相当于交换 \(b_i,b_j\)。所以若此时有 \(b_j<b_i\),我们一定会如此操作使得花费降低,于是\(a_i\) 连续的前提下,最终 \(a_i\) 大的位置对应的 \(b_i\) 一定小。

由以上三点我们可以得到,任一符合题意的序列 \(S\),它的花费在取到 \(f(S)\) 时一定有 \(a_i\) 两两不同,且在以 \(a_i\) 为关键字形成的每个连续段中 \(b_i\) 递减。

回到题目所求的问题上,我们发现问题可以看作向序列 \(P\) 中逐一添加元素并动态求解 \(f(P)\)。由于操作可逆,我们直接在当前最优解的序列和花费上添加新元素,与题意是等价的。

若新元素的 \(a_i\) 在序列中出现过,则需要连续使用操作一,直至 \(a_i\) 未出现过。此时新元素的 \(a_i\) 为原来出现过的 \(a_i\) 所在连续段中的 \(\max a_i+1\)经过这步操作后,\(a_i\) 最大可能达到 \(n+V-1\),所以相应的数组大小需开两倍。

此时新元素与原序列中的 \(a_i\) 都不相同。原序列已经取得最优代价,所以一定由若干满足 \(b_i\) 递减的连续段组成,而新元素显然也可以视作一个这样的长度为 \(1\) 的连续段,于是我们只需考虑将两个相接的连续段合并为一个更大的连续段所产生的花费即可。

\(x,y\) 为两个相接的连续段,也即 \(\max\limits_{x}a_i+1=\min\limits_{y}a_i\)。记 \(calc(op,num)\) 为连续段 \(op\) 中满足 \(b_i>num\) 的元素数量。

首先对 \(y\) 中的每个元素进行 \(|x|\) 次操作二,使得 \(x,y\) 的左端点对齐,并记左端点的下标为 \(0\)

考虑 \(x\) 中的每个元素,它在合并后的连续段中位置显然应为 \(calc(x,b_i)+calc(y,b_i)\),而它当前的位置是 \(calc(x,b_i)\),也就是说它应进行 \(calc(y,b_i)\) 次操作一,也即 \(y\) 中每个 \(b\) 值超过 \(b_i\) 的元素都会对花费产生 \(b_i\) 的贡献。\(y\) 中的元素同理。也可以感性理解为每个元素都需要用操作一来“越过”\(b\) 值超过它的元素。

用并查集维护每个连续段中 \(a_i\) 的最大值,用线段树维护最大值为 \(a_i\) 的连续段的信息,在每个线段树节点上记录 \(b_i\) 在对应区间内的元素数量及它们的 \(b_i\) 的和,线段树合并即可实现上面的分析。时间复杂度 \(\mathcal O(n\log n)\)干到了 CF 运行时间 rk9

#include<cstdio>
#include<numeric>
using namespace std;
const int N=4e5+2;
int n,cnt,fa[N],rt[N];long long ans;
struct node{
    int l,r,siz;long long sum;
}t[N*10];
int getfa(int x){
    return x==fa[x]?x:fa[x]=getfa(fa[x]);
}
void add(int &x,int l,int r,int k){
    x=++cnt;
    t[x].siz=1,t[x].sum=k;
    if(l==r) return;
    int mid=(l+r)>>1;
    k>mid?add(t[x].r,mid+1,r,k):
        add(t[x].l,l,mid,k);
}
void merge(int &x,int y,int l,int r){
    if(!y) return;
    if(!x){x=y;return;}
    ans+=t[t[x].l].sum*t[t[y].r].siz
        +t[t[y].l].sum*t[t[x].r].siz;
    int mid=(l+r)>>1;
    merge(t[x].l,t[y].l,l,mid);
    merge(t[x].r,t[y].r,mid+1,r);
    t[x].siz+=t[y].siz,t[x].sum+=t[y].sum;
}
void work(int x,int y){
    fa[x]=y;
    ans-=t[rt[y]].sum*t[rt[x]].siz;
    merge(rt[y],rt[x],1,n);
}
int main(){
    iota(fa,fa+N,0);
    scanf("%d",&n);
    for(int i=1,x,k;i<=n;++i){
        scanf("%d%d",&x,&k);
        if(rt[x]){
            ans+=(getfa(x)+1ll-x)*k;
            x=getfa(x)+1;
        }
        add(rt[x],1,n,k);
        if(rt[x-1]) work(x-1,x);
        if(rt[x+1]) work(x,getfa(x+1));
        printf("%lld\n",ans);
    }
    return 0;
}
posted @ 2022-10-25 11:20  XG0000  阅读(53)  评论(0)    收藏  举报