peiwenjun's blog 没有知识的荒原

P5064 [Ynoi Easy Round 2014] 等这场战争结束之后 题解

题目描述

给定 \(n\) 个点的图,初始没有边,点有点权 \(a_i\)

接下来 \(m\) 次操作:

  • 1 x y :在 \(x\)\(y\) 之间添加一条双向边。
  • 2 x :回退到第 \(x\) 次操作后的图。
  • 3 x y :查询 \(x\) 所在连通块第 \(y\) 小权值,若不存在则输出 -1

数据范围

  • \(1\le n,m\le 10^5,0\le a_i\le 10^9\)
  • 对操作 \(1,3\)\(1\le x,y\le n\)
  • 对操作 \(2\) ,若当前为第 \(i\) 次操作,保证 \(0\le x\le i-1\)

时间限制 \(\texttt{500ms}\) ,空间限制 \(\texttt{20MB}\)

分析

双向边查询连通块,并查集肯定用得上。

对于回退操作,下面是固定的解决套路,建议积累下来:

  • 如果只回退到上一个版本,使用可撤销并查集
  • 如果每次操作在之前任一版本基础上修改,使用可持久化并查集
  • 如果每次操作要么回退到之前任一版本,要么在最新版本上修改(即本题情形),建操作树后使用可撤销并查集

\(2,3\) 种情形的区别是,前者单次操作是 "回退+修改" ,后者单次操作是 "回退 or 修改" 。

这里操作树的本质是改变了操作的生效顺序。按照输入顺序(即时间序),我们需要处理 "回退到任一版本" 操作;但是在操作树中,我们把当前操作 "嫁接" 到回退后的版本之下,相当于新开了一个分支。在遍历完当前分支后只需回退到操作树的父节点,使用可撤销并查集即可。

代码实现时,如果是第 \(2\) 类操作,则操作树中它的父节点为 \(x\) ,否则为 \(i-1\)

至此,我们通过操作树,将第 \(2\) 类操作转化为 "回退到上一个版本"。


查询集合第 \(k\) 小,常见做法要么二分,要么分块。

如果选择二分,将 \(\le mid\) 的权值赋成 \(1\) ,其余赋成 \(0\) ,我们需要判断 \(x\) 所在连通块权值和是否 \(\ge y\)

然后问题来了。虽然对单个 \(mid\) ,我们可以轻松维护每个连通块权值和,但是当 \(mid\) 变化的时候,我们不可能对每个 \(mid\) 都维护一遍,而且没有办法整体二分,因此这条路行不通。

接下来考虑分块。离散化后对值域分块,记块长为 \(B\) ,先判断答案落在哪个块内。

这一部分是容易的,对每个点用一个长为 \(\frac nB\) 的数组维护每个点所在连通块中有多少个数,加边删边动态更新,回答询问时扫一遍数组即可。

接下来只需逐一判断值域中的每个数(不超过 \(B\) 个)在连通块中出现了多少次,一直累加到刚好超过 \(y\) 即为答案。

不妨在离散化时不执行去重操作,这样值域中的每个数和图中每个点一一对应。我们只需判断这 \(\mathcal O(B)\) 个点是否和 \(x\) 在同一连通块中,刚好可以通过可撤销并查集解决。

时间复杂度 \(\mathcal O((n+m)\frac nB+mB\log n)\) ,取 \(B=\sqrt n\) ,但是很不幸, \(\mathcal O(n\sqrt n)\) 的空间根本吃不消。


发现问题出在遍历整块时开不下 \(\mathcal O(n\cdot\frac nB)\) 的数组,那就转换思路,对操作树跑 \(\frac nB\)dfs ,每次只统计一个块中的元素个数。

注意到 dfs 主体由于用到可撤销并查集所以自带一只 \(\log\) ,但是由于操作树是静态的,因此每轮加边和删边的顺序固定。对于操作 \(1,3\) ,预处理并查集中 \(x,y\) 的根节点,这样可以把 \(\mathcal O(m\cdot\frac nB\cdot\log n)\) 转化为 \(\mathcal O(m\cdot\frac nB+m\log n)\) ,降低时间复杂度。

时间复杂度 \(\mathcal O(m\cdot\frac nB+mB\log n)\) ,空间复杂度 \(\mathcal O(n)\)

\(B=\sqrt{\frac n{\log n}}\) 则时间复杂度为 \(\mathcal O(m\sqrt{n\log n})\) ,但实际上由于 dfs 主体常数较大,第 \(3\) 类操作卡不满 \(m\) 个而且可撤销并查集常数很小,需要适当调大块长,实测取 \(B=2000\) 可以无压力通过。

#include<bits/stdc++.h>
#define fi first
#define se second
#define mp make_pair
#define pii pair<int,int>
using namespace std;
const int B=2000,maxn=1e5+5;
int m,n,top;
pii p[maxn];
vector<int> g[maxn];
int f[maxn],st[maxn],sz[maxn];
struct node
{
    int op,x,y,u,v,res;
}o[maxn];
int find(int x)
{
    return f[x]==x?x:find(f[x]);
}
void roll(int lim)
{
    while(top>lim)
    {
        int u=st[top--],v=f[u];
        f[u]=u,sz[v]-=sz[u];
    }
}
void dfs1(int k)
{
    int now=top;
    if(o[k].op==1)
    {
        int u=find(o[k].x),v=find(o[k].y);
        if(u!=v)
        {
            if(sz[u]>sz[v]) swap(u,v);
            f[u]=v,sz[v]+=sz[u],st[++top]=u;
            o[k].u=u,o[k].v=v;
        }
    }
    if(o[k].op==3) o[k].u=find(o[k].x),o[k].res=-1;
    for(auto v:g[k]) dfs1(v);
    roll(now);
}
void dfs2(int k,int id)
{
    int now=top;
    if(o[k].op==1&&o[k].u)
    {
        int u=o[k].u,v=o[k].v;
        f[u]=v,sz[v]+=sz[u],st[++top]=u;
    }
    if(o[k].op==3)
    {
        if(id&&o[k].res==-1)
        {///用 n/B 轮操作判断答案落在哪一块,如果 y 过大则 o[k].res=-1
            if(sz[o[k].u]>=o[k].y) o[k].res=id;
            else o[k].y-=sz[o[k].u];
        }
        if(!id&&o[k].res!=-1)
        {///最后扫描块内元素确定答案
            for(int i=(o[k].res-1)*B+1;;i++)
            {
                assert(i<=n);
                if(!(o[k].y-=(find(p[i].se)==o[k].u)))
                {
                    o[k].res=p[i].fi;
                    break;
                }
            }
        }
    }
    for(auto v:g[k]) dfs2(v,id);
    roll(now);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&p[i].fi),p[i].se=i;
    sort(p+1,p+n+1);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&o[i].op,&o[i].x);
        if(o[i].op==2) g[o[i].x].push_back(i);
        else scanf("%d",&o[i].y),g[i-1].push_back(i);
    }
    for(int j=1;j<=n;j++) f[j]=j,sz[j]=1;
    dfs1(0);
    for(int i=1;i<=(n-1)/B+1;i++)
    {
        for(int j=1;j<=n;j++) f[j]=j,sz[p[j].se]=(i-1)*B+1<=j&&j<=i*B;
        dfs2(0,i);
    }
    dfs2(0,0);
    for(int i=1;i<=m;i++) if(o[i].op==3) printf("%d\n",o[i].res);
    return 0;
}

posted on 2025-08-27 20:15  peiwenjun  阅读(17)  评论(0)    收藏  举报

导航