【JZOJ5798】【2018提高组】模拟A组 树 (并查集+LCA)

Problem

我们有一颗从1到n编号的n(<=300000)个结点的树,此外,您将从树中获得M(<=300000)个节点对,形式为(a1,b1),(a2,b2),…(am,bm).
我们需要给每一条边定向,使得每一对节点对存在一条从ai到bi或从bi到ai的路径。
现在要求方案数,对10^9+7取mod即可。

Solution

  • 刚看这道题,感觉很神仙。
  • 仔细分析,对于一个点对(a,b),显然a到b的路径上的边只要确定一条的方向,其他的均可确定。
  • 那么可以将这些边丢到同一个并查集中。(并查集中的每一个点代表原图中的一条边)那么最终的答案即为 2 s (s为并查集数)。
  • 如何实现呢?我先讲一下我的SB方法。

我的SB方法

  • 具体地说,对于点对(a,b),找出其lca,设为点c。我们使用倍增找出c到a路径上第二个点x和c到b路径上第二个点y(x、y是c的儿子),那么我们让并查集中的 c x 连向 c y
    这里写图片描述
  • 在上图中,我们先让边3连向边4。然后,我们再dfs一波,让1连向2,2连向3,6连向5,5连向4。
  • 具体地说,我们可以采用树上差分。在a、b处各打一个+1,在x、y处各打一个-1。每个点的值大于0则表明它连向它父亲的边 应连向 它父亲连向它祖父的边。举例来说,点a的值就大于0,那么边1就应连向边2。

  • 但是这么做还有一个问题:那就是无法处理答案=0的情况。
  • 答案之所以会=0,无非是因为有些点对矛盾了。
  • 我们可以记录一下并查集中的每个点是否和它的父亲同向。(并查集中的每一个点代表原图中的一条边)

  • 这么做的话,路径压缩就要改一下。
  • 我们可以求个后缀异或和。
  • 举例来说,对于 1 2 3 4 x y 表示y为x的父亲),我们要将1路径压缩,即将原树变成 1 4 , 2 4 , 3 4 的形态。若1与2反向,2与3同向,3与4反向,我们倒着来做,求一波后缀和,于是可知3与4反向,2与4反向,1与4同向。

  • 还有连边的时候也要有所修改。
  • 对于 x y ,我们都是让x和y路径压缩一波,然后设它们的父亲分别为fx、fy,令fx连向fy。
  • 考虑一下,若x与fx反向,y与fy同向,我们要从x连与y同向的边,那么就应从fx连与fy反向的边。
  • 实际上,fx连向fy的边即为x与fx的边、y与fy的边、x与y的边的异或和。

  • 那么何时会出现矛盾呢?
  • 当x和y位于同一个并查集中时,路径压缩后,fx=fy。设若此时我们要从fx连与fy反向的边,即从fx连与fx反向的边;但fx肯定与fx同向,这就产生矛盾。
  • 所以,一出现这种情况,直接输出0即可。

  • 时间复杂度: O ( m l o g 2 n + n α ( n ) )
Code
#include <bits/stdc++.h>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
#define rep(i,x) for(edge *i=x; i; i=i->ne)
#define P {putchar(48); exit(0);}
#define fs(x) for(it=x.begin();it!=x.end();it++)
using namespace std;

const int N=3e5+1,M=1e9+7;
int i,n,m,x,y,sum[N],top,sta[N],f[N],dep[N],anc[N][19],a,b,q,fa[N],d[N],ans;
bool vis[N],re[N];
struct edge
{
    int v;
    edge *ne;
    edge(int v,edge *ne):v(v),ne(ne){}
}*fin[N],*cur[N];

void read(int&x)
{
    char ch=' '; x=0;
    while(!isdigit(ch)) ch=getchar();
    while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48), ch=getchar();
}

inline void link(int x,int y)
{
    fin[x]=new edge(y,fin[x]);
}

void dfs(int x)
{
    sta[top=1]=x;
    while(top)
    {
        loop:x=sta[top];
        if(!vis[x])
        {
            vis[x]=1; cur[x]=fin[x]; anc[x][0]=f[x];
            fo(i,1,18) anc[x][i]=anc[anc[x][i-1]][i-1];
        }

        rep(i,cur[x])
        {
            y=i->v;
            if(y!=f[x])
            {
                f[y]=x; dep[y]=dep[x]+1;
                sta[++top]=y; cur[x]=i->ne; goto loop;
            }
        }

        top--; 
    }
}

int lca(int x,int y)
{
    if(dep[x]<dep[y]) swap(x,y);
    int i,fx,fy;
    fd(i,18,0) if((fx=anc[x][i])&&dep[fx]>=dep[y]) x=fx;
    if(x!=y)
        fd(i,18,0)
            if((fx=anc[x][i])!=(fy=anc[y][i]))
                x=fx, y=fy;
    return x==y ? x : f[x];
}
int mindep(int x,int y)
{
    int i,f;
    fd(i,18,0) if((f=anc[x][i])&&dep[f]>dep[y]) x=f;
    return x;
}

int gef(int x)
{
    d[0]=0;
    for(; x!=fa[x]; x=fa[x]) d[++d[0]]=x;
    bool s=0;
    while(d[0]) 
    {
        s^=re[d[d[0]]];
        fa[d[d[0]]]=x;
        re[d[d[0]--]]=s;
    }
    return x;
}
void Union(int x,int y,bool k)
{
    int fx=gef(x), fy=gef(y);
    k=k^re[x]^re[y];
    if(fx==fy&&k) P; 
    fa[fx]=fy; re[fx]=k;
}

void dfs1(int x)
{
    sta[top=1]=x;
    while(top)
    {
        loop:x=sta[top];
        if(vis[x]) vis[x]=0, cur[x]=fin[x]; 

        rep(i,cur[x])
        {
            y=i->v;
            if(y!=f[x])
            {
                sta[++top]=y; 
                cur[x]=i->ne; goto loop;
            }
        }

        sum[f[x]]+=sum[x];
        if(sum[x]>0) Union(x,f[x],0);
        top--; 
    }
}

int main()
{
    freopen("usmjeri.in","r",stdin);
    freopen("usmjeri.out","w",stdout);
    read(n); read(m);
    fo(i,1,n-1)
    {
        read(x); read(y);
        link(x,y); link(y,x); 
    }

    dfs(1); 

    fo(i,1,n) fa[i]=i;
    fo(i,1,m)
    {
        read(a); read(b); 
        if(a==b) continue;
        q=lca(a,b); 
        if(q==a||q==b)
        {
            if(q==a)
                    x=mindep(b,a), sum[b]++;
            else    x=mindep(a,b), sum[a]++;
            sum[x]--; continue;
        }
        sum[a]++; sum[b]++;
        x=mindep(a,q); sum[x]--;
        y=mindep(b,q); sum[y]--;
        Union(x,y,1);
    }

    dfs1(1);

    ans=1;
    fo(i,2,n)
    {
        x=gef(i);
        if(vis[x]) continue;
        vis[x]=1; (ans<<=1)%=M;
    }
    printf("%d",ans);
}

正确的正解

  • 实际上,对于每一个点对(a,b),我们在找出c=lca(a,b)后,可以直接暴力连上a到c的边以及b到c的边。
    这里写图片描述
  • 比如对于上图,我们暴力将边1连上边2,边2连上边3,边6连上边5,边5连上边4。
  • 不过,我们连边是让深度较大的边连向深度较小的边。
  • 这样一来,处理完点对(a,b)后,如果有另一对(a’,b’),其lca为c’,且路径(a’,c’)完全覆盖了(a,c),则我们在连这段路径的边时会直接跳过整段的路径(a,b)。(我们要路径压缩)

  • 但是,如何将路径(a,c)和路径(b,c)弄到同一个并查集呢?
  • 其实我们不必找到x和y。
  • 设边a表示点a连向其父亲的边,譬如上图中的边a即为边1。我们可以直接将边a和边b连起来。

  • 但是这样会出现一个问题。
  • 边a和边b间连的边是表示它们反向(边a和边b反向)。这样,我们在暴力连其他的(a’,c’)路径时,可能(a’,c’)上的路径中有边反向。
  • 如果我们从某条边x,跳到了它所在的并查集的根,压缩路径,变成fa[x]后,直接判断边a是否和边fa[x]同向,那也依然有问题。因为可能x到fa[x]的路径上就是有两个点(并查集中的点代表原图中的一条边)反向,我们难以判断。

  • 囿于上述问题,我们可以先处理所有(a,c),(b,c)的路径,即先连同向的边。连完同向的边后,再扫一遍询问数组,为所有边a和边b再连上反向的边。

  • 时间复杂度: O ( m l o g 2 n + n α ( n ) )

Code

#include <bits/stdc++.h>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
#define rep(i,x) for(edge *i=x; i; i=i->ne)
#define P {putchar(48); exit(0);}
#define fs(x) for(it=x.begin();it!=x.end();it++)
using namespace std;

const int N=3e5+1,M=1e9+7;
int i,n,m,x,y,sum[N],top,sta[N],f[N],dep[N],anc[N][19],a,b,q,fa[N],d[N],ans;
bool vis[N],re[N];
struct edge
{
    int v;
    edge *ne;
    edge(int v,edge *ne):v(v),ne(ne){}
}*fin[N],*cur[N];
struct tuple
{
    int a,b,c;  
}t[N];

void read(int&x)
{
    char ch=' '; x=0;
    while(!isdigit(ch)) ch=getchar();
    while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48), ch=getchar();
}

inline void link(int x,int y)
{
    fin[x]=new edge(y,fin[x]);
}

void dfs(int x)
{
    sta[top=1]=x;
    while(top)
    {
        loop:x=sta[top];
        if(!vis[x])
        {
            vis[x]=1; cur[x]=fin[x]; anc[x][0]=f[x];
            fo(i,1,18) anc[x][i]=anc[anc[x][i-1]][i-1];
        }

        rep(i,cur[x])
        {
            y=i->v;
            if(y!=f[x])
            {
                f[y]=x; dep[y]=dep[x]+1;
                sta[++top]=y; cur[x]=i->ne; goto loop;
            }
        }

        top--; 
    }
}

int lca(int x,int y)
{
    if(dep[x]<dep[y]) swap(x,y);
    int i,fx,fy;
    fd(i,18,0) if((fx=anc[x][i])&&dep[fx]>=dep[y]) x=fx;
    if(x!=y)
        fd(i,18,0)
            if((fx=anc[x][i])!=(fy=anc[y][i]))
                x=fx, y=fy;
    return x==y ? x : f[x];
}

int gef(int x)
{
    d[0]=0;
    for(; x!=fa[x]; x=fa[x]) d[++d[0]]=x;
    bool s=0;
    while(d[0]) 
    {
        s^=re[d[d[0]]];
        fa[d[d[0]]]=x;
        re[d[d[0]--]]=s;
    }
    return x;
}
void go(int x)
{
    for(x=gef(x); dep[x]>dep[q]+1; x=gef(x)) fa[x]=gef(f[x]);
}

int main()
{
    freopen("usmjeri.in","r",stdin);
    freopen("usmjeri.out","w",stdout);
    read(n); read(m);
    fo(i,1,n-1)
    {
        read(x); read(y);
        link(x,y); link(y,x); 
    }

    dfs(1); 

    fo(i,1,n) fa[i]=i;
    fo(i,1,m)
    {
        read(a); read(b); 
        q=lca(a,b);
        go(a); go(b);
        t[i]={a,b,q};
    }

    fo(i,1,m)
    {
        a=t[i].a; b=t[i].b; q=t[i].c;
        if(a==q|b==q) continue;
        int fx=gef(a), fy=gef(b);
        if(fx==fy) 
        {
            if(re[a]==re[b]) P;
            continue;
        }
        fa[fx]=fy; re[fx]=re[a]==re[b];
    }

    ans=1;
    fo(i,2,n)
    {
        x=gef(i);
        if(!vis[x]) continue;
        vis[x]=0; (ans<<=1)%=M;
    }
    printf("%d",ans);
}

猜你喜欢

转载自blog.csdn.net/qq_36551189/article/details/81609992