P5064 [Ynoi2014] 等这场战争结束之后

题意

给定一个图,每个点有点权,最开始没有边。

有一些操作:

  1. 添加一条 $x$ 与 $y$ 之间的双向边。
  2. 回到第 $x$ 次操作后的状态(注意这里的 $x$ 可以是 $0$,即回到初始状态)。
  3. 查询 $x$ 所在联通块能到的点中点权第 $k$ 小的值,如果不存在,那么输出 $-1$。

Solution

发现没有强制在线,考虑将操作离线并建立操作树。对于 $1,3$ 操作,将 $i$ 向 $i-1$ 连一条边即可。对于 $2$ 操作,向 $x$ 连一条边,最后对于操作树 dfs 求出所有答案即可。

在 dfs 时,若对于每个询问暴力查找第 $k$ 小数显然时间复杂度不正确。考虑优化,发现对于点权离散化后值域为 $[1,10^6]$,对值域分块。对于每个连通块而言,维护其在每个值域块内的节点个数,这样查找答案的最坏复杂度为 $\mathcal{O}(\sqrt{n})$。在操作 $1$ 和回溯时暴力合并和分离信息即可,加上可撤销并查集,这样做时间复杂度是 $\mathcal{O}(\sqrt{n}\log n)$ 。

故总时间复杂度为 $\mathcal{O}(n\sqrt{n}\log n )$,空间复杂度为 $\mathcal{O}(n\sqrt{n})$。

但是由于毒瘤 lxl 把空间开到了奇怪的地步,调调块长然后开 short 即可在时间限制和空间限制内通过本题。

实测块长开成 $2500$ 能过,跑的还蛮快的。

code

#include<bits/stdc++.h>
using namespace std;
inline int read()
{
    int res=0,flag=1;
    char ch=getchar();
    while(!isalnum(ch)) (ch=='-')?flag=-1:1,ch=getchar();
    while(isalnum(ch)) res=res*10+ch-'0',ch=getchar();
    return res*flag;
} 
inline void write(int x)
{
    char buf[20]; int len=0;
    if(x<=0) putchar((x==0)?'0':'-'); x=abs(x);
    while(x!=0) buf[++len]=x%10+'0',x/=10;
    while(len!=0) putchar(buf[len--]);
    putchar('\n');
    return ;
}
struct edge
{
    int to,nxt;
};
struct node 
{
    short opt;
    int x,y;
};
struct edge ed[100010];
struct node nd[100010];
int n,m,tot,len,sum;
int val[100010];
int id[100010],fa[100010];
int head[200010];
int ans[100010];
int size[100010];
unsigned short data[100010][41];
void init()
{
    n=read(),m=read();
    len=min(n,2500),sum=ceil((double)(n/len));
    for(int i=1;i<=n;i++)
        val[i]=read(),fa[i]=id[i]=i;
    sort(id+1,id+n+1,[](int a,int b)->bool{return val[a]<val[b];});
    for(int i=1,cnt=1;i<=n;i++)
    {
        size[i]=data[id[i]][cnt]=1;
        if(i%len==0)
            cnt++;
    }
    return ;
}
void add_edge(int fr,int to)
{
    ed[++tot]=(edge){to,head[fr]};
    head[fr]=tot;
    return ;
}
int find(int x)
{
    if(fa[x]==x)
        return x;
    return find(fa[x]);
}
void add(int &x,int &y)
{
    x=find(x),y=find(y);
    if(x==y) return ;
    if(size[x]<size[y])
        swap(x,y);
    fa[y]=x;
    size[x]+=size[y];
    for(int i=1;i<=sum;i++)
        data[x][i]+=data[y][i];
    return ;
}
void del(int x,int y)
{
    if(x==y) return ;
    fa[y]=y;
    size[x]-=size[y];
    for(int i=1;i<=sum;i++)
        data[x][i]-=data[y][i];
    return ;
}
int query(int pos,int k)
{
    pos=find(pos);
    if(size[pos]<k)
        return -1;
    for(int i=1;i<=sum;i++)
    {
        if(k-data[pos][i]>0)
        {
            k-=data[pos][i];
            continue;
        }
        for(int j=len*(i-1)+1;j<=len*i+1;j++)
        {
            if(find(id[j])==pos) k--;
            if(k==0) return val[id[j]];
        }
    }
    return -1;
}
void dfs(int fr)
{
    if(nd[fr].opt==1)
        add(nd[fr].x,nd[fr].y);
    if(nd[fr].opt==3)
        ans[fr]=query(nd[fr].x,nd[fr].y);
    for(int i=head[fr];i!=0;i=ed[i].nxt)
        dfs(ed[i].to);
    if(nd[fr].opt==1)
        del(nd[fr].x,nd[fr].y);
    return ;
}
int main(int argc,const char *argv[])
{
    init(); 
    for(int i=1;i<=m;i++)
    {
        nd[i].opt=read();
        if(nd[i].opt==2)
        {
            nd[i].x=read();
            add_edge(nd[i].x,i);
            continue;
        }
        nd[i].x=read();
        nd[i].y=read();
        add_edge(i-1,i);
    }
    dfs(0);
    for(int i=1;i<=m;i++)
        if(nd[i].opt==3)
            write(ans[i]);
    return 0;
}
posted @ 2023-08-03 22:02  Che_001  阅读(19)  评论(0)    收藏  举报  来源