bzoj4530[Bjoi2014]大融合 线段树合并+dfs序+并查集

9 篇文章 0 订阅
4 篇文章 0 订阅

题意比较明显就不说了。
这题我思考了一会儿,由于题解并不说清楚为什么这样处理的原因,我想了很久才想通= =。
建议看完理解题解以后不要看标,自己独立打出来,这题的思路很妙~。。

首先很容易知道先把原树建出来以后,答案就是(size(fa)-size(x))*size(x)
那么问题是怎么样处理询问呢,当然你可以树剖暴力搞,因为我一开始也是这么想的,这里提供一种更优美的解法= =
我们把树建出来以后,在图上建一颗权值线段树(dfs,顺便求出dfs序),同时用并查集去维护添加操作,同时合并权值线段树在并查集中以find(x),find(y)为根的节点。
一开始我并不理解为什么要合并,感觉不合并到时候照样查询啊,只不过会T吧。
事实证明我很naive,因为每次查询的时候我并查集合并完了,他的根已经不是之前的根了,所以必须在并查集合并的同时对权值线段树进行合并= =。

感觉还是说的不是很清楚,看看代码可能理解的更深刻,有什么不懂的在下面评论区留言吧。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=1e5+5;
struct node
{
    int x,y,id;
}b[N];
typedef long long ll;
int fa[N],in[N],out[N],dep[N],vis[N];
int n,id,q,x,y,num;
int head[N<<1],next[N<<1],go[N<<1];
int ls[N*20],rs[N*20],root[N*20],size[N*20];
char ch[5];
int tot,cnt;
inline void add(int x,int y)
{
    go[++tot]=y;
    next[tot]=head[x];
    head[x]=tot;
}
inline int merge(int x,int y)
{
    if (!x)return y;
    if (!y)return x;
    size[x]+=size[y];
    ls[x]=merge(ls[x],ls[y]);
    rs[x]=merge(rs[x],rs[y]);
    return x;
}
inline int query(int x,int l,int r,int l1,int r1)
{
    int mid=(l+r)>>1;
    if (l==l1&&r==r1)return size[x];
    if (r1<=mid)return query(ls[x],l,mid,l1,r1);
    else if (l1>mid)return query(rs[x],mid+1,r,l1,r1);
    else return query(ls[x],l,mid,l1,mid)+query(rs[x],mid+1,r,mid+1,r1);
}
inline void ins(int &x,int l,int r,int v)
{
    if (!x)x=++num;
    int mid=(l+r)>>1;
    if (l==r)
    {
        size[x]=1;
        return;
    }
    if (v<=mid)ins(ls[x],l,mid,v);
    else ins(rs[x],mid+1,r,v);
    size[x]=size[ls[x]]+size[rs[x]];
}
inline void dfs(int x,int fa)
{
    in[x]=++cnt;
    vis[x]=1;
    ins(root[x],1,n,cnt);
    for(int i=head[x];i;i=next[i])
    {
        int v=go[i];
        if (v!=fa)
        {
            dep[v]=dep[x]+1;
            dfs(v,x);
        }
    }
    out[x]=cnt;
}
int find(int x)
{
    if (fa[x]!=x)fa[x]=find(fa[x]);
    return fa[x];
}
int main()
{
    scanf("%d%d",&n,&q);
    fo(i,1,q)
    {
        scanf("%s",ch);
        scanf("%d%d",&b[i].x,&b[i].y);
        if (ch[0]=='A')
        {
            b[i].id=1;
            add(b[i].x,b[i].y);
            add(b[i].y,b[i].x);
        }
        else b[i].id=2;
    }
    fo(i,1,n)if (!vis[i])dfs(i,0);
    fo(i,1,n)fa[i]=i;
    fo(i,1,q)
    {
        if (b[i].id==1)
        {
            int x=find(b[i].x),y=find(b[i].y);
            if (dep[x]>dep[y])swap(x,y);
            root[x]=merge(root[x],root[y]);
            fa[y]=x;
        }
        if (b[i].id==2)
        {
            int x=b[i].x,y=b[i].y;
            if (dep[x]<dep[y])swap(x,y);
            int fa=find(x),v=query(root[fa],1,n,in[x],out[x]);
            printf("%lld\n",1ll*(size[root[fa]]-v)*v);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值