JZOJ 3766. 【BJOI2014】大融合

38 篇文章 0 订阅
15 篇文章 0 订阅

Description

小强要在N个孤立的星球上建立起一套通信系统。这套通信系统就是连接N个点的一个树。这个树的边是一条一条添加上去的。在某个时刻,一条边的负载就是它所在的当前能够联通的树上路过它的简单路径的数量。

Description

例如,在上图中,现在一共有了5条边。其中,(3,8)这条边的负载是6,因为有六条简单路径2-3-8,2-3-8-7,3-8,3-8-7,4-3-8,4-3-8-7路过了(3,8)。

现在,你的任务就是随着边的添加,动态的回答小强对于某些边的负载的询问。

Input

第一行包含两个整数N,Q,表示星球的数量和操作的数量。星球从1开始编号。

接下来的Q行,每行是如下两种格式之一:

A x y 表示在x和y之间连一条边。保证之前x和y是不联通的。

Q x y 表示询问(x,y)这条边上的负载。保证x和y之间有一条边。

Output

对每个查询操作,输出被查询的边的负载。

Sample Input

8 6

A 2 3

A 3 4

A 3 8

A 8 7

A 6 5

Q 3 8

Sample Output

6

Data Constraint

对于40%的数据,N,Q≤1000

对于100%的数据,1≤N,Q≤100000

Solution

  • 这题的思路很巧妙。考虑树链剖分和并查集。

  • 先把边都连上,形成一些森林,处理树链剖分的有关信息。

  • 接着再逐个连边,把深度高的点设为x,深度低的点设为y,y所在的链链顶为z。

  • 再设x的子树大小为m1,z的子树大小为m2,有如下情况:

  • ①:连边操作,从y到z的子树大小全部加上x的子树大小(用线段树区间加实现)

  • ②:查询操作,则答案即为m1*(m2-m1)。

Code

#include<cstdio>
#include<algorithm>
using namespace std;
const int N=100001;
struct data
{
    bool z;
    int x,y;
}a[N];
struct segment
{
    int l,r,sum,c;
}g[N<<2];
int tot,num;
int first[N],next[N<<1],en[N<<1];
int fa[N],size[N],dep[N];
int top[N],son[N],tree[N],pre[N];
int f[N],t[N],h[N];
inline int read()
{
    int X=0,w=1; char ch=0;
    while(ch<'0' || ch>'9') {if(ch=='-') w=-1;ch=getchar();}
    while(ch>='0' && ch<='9') X=(X<<3)+(X<<1)+ch-'0',ch=getchar();
    return X*w;
}
inline void insert(int x,int y)
{
    next[++tot]=first[x];
    first[x]=tot;
    en[tot]=y;
}
inline int get(int x)
{
    if(f[x]==x) return x;
    return f[x]=get(f[x]);
}
inline void dfs1(int x)
{
    dep[x]=dep[fa[x]]+1;
    size[x]=1;
    for(int i=first[x];i;i=next[i])
        if(en[i]!=fa[x])
        {
            fa[en[i]]=x;
            dfs1(en[i]);
            size[x]+=size[en[i]];
            if(!son[x] || size[son[x]]<size[en[i]]) son[x]=en[i];
        }
}
inline void dfs2(int x,int y)
{
    top[pre[tree[x]=++num]=x]=y;
    if(!son[x]) return;
    dfs2(son[x],y);
    for(int i=first[x];i;i=next[i])
        if(en[i]!=fa[x] && en[i]!=son[x]) dfs2(en[i],en[i]);
}
inline void update(int v)
{
    int ls=v<<1,rs=ls|1;
    if(g[v].c)
    {
        g[ls].sum+=g[v].c;
        g[ls].c+=g[v].c;
        g[rs].sum+=g[v].c;
        g[rs].c+=g[v].c;
        g[v].c=0;
    }
}
inline void make(int v,int l,int r)
{
    g[v].l=l,g[v].r=r;
    if(l==r)
    {
        g[v].sum=1;
        return;
    }
    int mid=(l+r)>>1;
    make(v<<1,l,mid);
    make(v<<1|1,mid+1,r);
    g[v].sum=g[v<<1].sum+g[v<<1|1].sum+1;
}
inline void change(int v,int x,int y,int z)
{
    if(g[v].l==x && g[v].r==y)
    {
        g[v].sum+=z;
        g[v].c+=z;
        return;
    }
    update(v);
    int mid=(g[v].l+g[v].r)>>1;
    if(y<=mid) change(v<<1,x,y,z); else
        if(x>mid) change(v<<1|1,x,y,z); else
        {
            change(v<<1,x,mid,z);
            change(v<<1|1,mid+1,y,z);
        }
}
inline int find(int v,int x)
{
    if(g[v].l==g[v].r) return g[v].sum;
    update(v);
    int mid=(g[v].l+g[v].r)>>1;
    if(x<=mid) return find(v<<1,x);
    return find(v<<1|1,x);
}
int main()
{
    int n=read(),q=read();
    for(int i=1;i<=q;i++)
    {
        char ch=getchar();
        while(ch!='A' && ch!='Q') ch=getchar();
        a[i].x=read(),a[i].y=read();
        if(a[i].z=ch=='A')
        {
            insert(a[i].x,a[i].y);
            insert(a[i].y,a[i].x);
        }
    }
    for(int i=1;i<=n;i++)
        if(!size[i])
        {
            dfs1(i);
            dfs2(i,i);
        }
    make(1,1,n);
    for(int i=1;i<=n;i++) h[t[f[i]=i]=i]=1;
    for(int i=1;i<=q;i++)
    {
        int x=a[i].x,y=a[i].y;
        if(dep[x]<dep[y]) swap(x,y);
        int f1=get(x),f2=get(y);
        if(a[i].z)
        {
            int n1=y,n2=t[f2];
            int m1=top[n1],m2=top[n2];
            if(dep[m1]<dep[t[f2]]) m1=t[f2];
            if(dep[m2]<dep[t[f2]]) m2=t[f2];
            while(m1!=m2)
            {
                if(dep[m1]<dep[m2]) swap(m1,m2),swap(n1,n2);
                change(1,tree[m1],tree[n1],h[f1]);
                n1=fa[m1],m1=top[n1];
                if(dep[m1]<dep[t[f2]]) m1=t[f2];
            }
            if(dep[n1]>dep[n2]) swap(n1,n2);
            change(1,tree[n1],tree[n2],h[f1]);
            h[f2]+=h[f1];
            if(dep[t[f1]]<dep[t[f2]]) t[f2]=t[f1];
            f[f1]=f2;
        }else
        {
            int m1=find(1,tree[x]),m2=find(1,tree[t[f2]])-m1;
            printf("%lld\n",(long long)m1*m2);
        }
    }
    return 0;
}

方法二

  • 显然这道题也可以用可维护子树信息的LCT。

  • 那么 LCT 如何维护子树信息呢?(以维护子树大小为例)

  • 我们可以多开一个数组记录一个点虚树上的大小信息,合并时直接并到该点上。

  • 我们发现只有当 Access 或 Link 操作时会影响到信息记录(其他操作都不会影响)。

  • 当 Access 时,只需将虚树信息加上原右儿子的 Size ,再减去新右儿子的 Size 即可。

  • 而当 Link 时,相当于 x x y 添一条虚边,

  • 于是除了 makeroot(x) 外,再执行一遍 makeroot(y),(消除修改 y y 对其子树各节点信息的影响)

  • 最后再将 y 虚树信息加上 x x 的 Size 即可。

  • 时间复杂度 O(N log N)

Code2

#include<cstdio>
#include<algorithm>
#include<cctype>
using namespace std;
const int N=1e5+5;
int top;
int fa[N],size[N],s[N][2],g[N],st[N];
bool rev[N];
inline int read()
{
    int X=0,w=0; char ch=0;
    while(!isdigit(ch)) w|=ch=='-',ch=getchar();
    while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
    return w?-X:X;
}
inline bool pd(int x)
{
    return x==s[fa[x]][1];
}
inline bool isroot(int x)
{
    return x^s[fa[x]][0] && x^s[fa[x]][1];
}
inline void reverse(int x)
{
    if(x) swap(s[x][0],s[x][1]),rev[x]^=1;
}
inline void update(int x)
{
    size[x]=size[s[x][0]]+size[s[x][1]]+1+g[x];
}
inline void down(int x)
{
    if(rev[x])
    {
        reverse(s[x][0]),reverse(s[x][1]);
        rev[x]=false;
    }
}
inline void rotate(int x)
{
    int y=fa[x],w=pd(x);
    if(s[y][w]=s[x][w^1]) fa[s[y][w]]=y;
    if((fa[x]=fa[y]) && !isroot(y)) s[fa[y]][pd(y)]=x;
    s[fa[y]=x][w^1]=y;
    update(y);
}
inline void splay(int x)
{
    for(int y=st[top=1]=x;!isroot(y);y=fa[y]) st[++top]=fa[y];
    while(top) down(st[top--]);
    for(int y;!isroot(x);rotate(x))
        if(!isroot(y=fa[x])) rotate(pd(x)==pd(y)?y:x);
    update(x);
}
inline void access(int x)
{
    for(int y=0;x;x=fa[y=x])
    {
        splay(x);
        g[x]+=size[s[x][1]];
        g[x]-=size[s[x][1]=y];
        update(x);
    }
}
inline void mkroot(int x)
{
    access(x),splay(x),reverse(x);
}
inline void link(int x,int y)
{
    mkroot(x),mkroot(y);
    g[fa[x]=y]+=size[x];
    update(y);
}
int main()
{
    int n=read(),m=read();
    for(int i=1;i<=n;i++) size[i]=1;
    while(m--)
    {
        char ch=getchar();
        int x=read(),y=read();
        if(ch=='A') link(x,y); else
        {
            mkroot(x),access(y),splay(y);
            printf("%lld\n",(long long)size[x]*(size[y]-size[x]));
        }
    }
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值