bzoj 4530(DFS序+线段树合并)

传送门
题解:对每个点建权值线段树(权值即点在DFS序列中的编号),合并的时候直接合并两个点根的线段树,并连一下并查集,查询的时候找到x,y所在树的根f,假设dep[x]>dep[y],那答案就是(size[f]-size[x])*size[x](这一点的解释尽快补上,不过好像不比较也能过,难道是数据水?)。
P.S.在询问时要选深度更深的点的原因:
如果dep[x] < dep[y]并且选择了x,那么x的子树就包括了y点,而答案是两边子树大小的乘积,所以这样会造成多余贡献,不合法。
这里写图片描述

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+4;
int n,m;
int head[MAXN],fa[MAXN],edge=0;
int dep[MAXN],in[MAXN],out[MAXN],tim=0;
bool vis[MAXN];
struct EDGE {
    int v,nxt;
}e[MAXN<<1];
struct Q {
    int opt,x,y;
}q[MAXN];
int siz[MAXN*18],lc[MAXN*18],rc[MAXN*18],root[MAXN],tot=0;
int find(int x) {
    return fa[x]==x?x:fa[x]=find(fa[x]);
}
inline void adde(int u,int v) {
    e[++edge].nxt=head[u],e[edge].v=v,head[u]=edge;
    e[++edge].nxt=head[v],e[edge].v=u,head[v]=edge;
}
inline int read() {
    int x=0;char c=getchar();
    while (c<'0'||c>'9') c=getchar();
    while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    return x;
}
inline void pushup(int rt) {
    siz[rt]=siz[lc[rt]]+siz[rc[rt]];
}
void insert(int &rt,int l,int r,int val) {
    rt=++tot;
    if (l==r) {siz[rt]=1;return ;}
    int mid=(l+r)>>1;
    if (val<=mid) insert(lc[rt],l,mid,val);
    else insert(rc[rt],mid+1,r,val);
    pushup(rt);
}
inline int merge(int a,int b) {
    if (!a) return b;
    if (!b) return a;
    lc[a]=merge(lc[a],lc[b]);
    rc[a]=merge(rc[a],rc[b]);
    pushup(a);
    return a;
}
int query(int rt,int l,int r,int L,int R) {
    if (L<=l&&r<=R) return siz[rt];
    int mid=(l+r)>>1,ret=0;
    if (L<=mid) ret+=query(lc[rt],l,mid,L,R);
    if (mid<R) ret+=query(rc[rt],mid+1,r,L,R);
    return ret;
}
void dfs(int p,int fa) {
    in[p]=++tim,vis[p]=true;
    insert(root[p],1,n,in[p]);
    for (int i=head[p];~i;i=e[i].nxt) {
        int v=e[i].v;
        if (v^fa)
            dep[v]=dep[p]+1,dfs(v,p);
    }
    out[p]=tim;
}
int main() {
//  freopen("bzoj 4530.in","r",stdin);
    memset(head,-1,sizeof(head));
    memset(vis,false,sizeof(vis));
    n=read(),m=read();
    for (register int i=1;i<=m;++i) {
        char ss;
        while (!isalpha(ss=getchar()));
        q[i].opt=(ss^'Q')?1:2;
        q[i].x=read(),q[i].y=read();
        if (q[i].opt&1) adde(q[i].x,q[i].y);
    }
    for (register int i=1;i<=n;++i){
        fa[i]=i;
        if (!vis[i]) dep[i]=0,dfs(i,0);
    }
/*  for (int i=1;i<=n;++i)
        cout<<dep[i]<<' ';
    cout<<endl;*/
    for (register int i=1;i<=m;++i) {
        if (q[i].opt&1) {
            int x=q[i].x,y=q[i].y;
            x=find(x),y=find(y);
            if (dep[x]>dep[y]) x^=y^=x^=y;//find the earlier-linked node(new node link to old node)
            root[x]=merge(root[x],root[y]);
            fa[y]=x;
        }
        else {
            int x=q[i].x,y=q[i].y;
            int pos=(dep[x]<dep[y])?y:x;//find the later-linked node to avoid repeated calculation
            int f=find(pos);
            int temp=query(root[f],1,n,in[pos],out[pos]);
            printf("%lld\n",1ll*(siz[root[f]]-temp)*temp);
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值