【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的路徑上的邊只要確定一條的方向,其他的均可確定。
  • 那麼可以將這些邊丟到同一個並查集中。(並查集中的每一個點代表原圖中的一條邊)那麼最終的答案即爲2s (s爲並查集數)。
  • 如何實現呢?我先講一下我的SB方法。

我的SB方法

  • 具體地說,對於點對(a,b),找出其lca,設爲點c。我們使用倍增找出c到a路徑上第二個點x和c到b路徑上第二個點y(x、y是c的兒子),那麼我們讓並查集中的cx 連向cy
    這裏寫圖片描述
  • 在上圖中,我們先讓邊3連向邊4。然後,我們再dfs一波,讓1連向2,2連向3,6連向5,5連向4。
  • 具體地說,我們可以採用樹上差分。在a、b處各打一個+1,在x、y處各打一個-1。每個點的值大於0則表明它連向它父親的邊 應連向 它父親連向它祖父的邊。舉例來說,點a的值就大於0,那麼邊1就應連向邊2。

  • 但是這麼做還有一個問題:那就是無法處理答案=0的情況。
  • 答案之所以會=0,無非是因爲有些點對矛盾了。
  • 我們可以記錄一下並查集中的每個點是否和它的父親同向。(並查集中的每一個點代表原圖中的一條邊)

  • 這麼做的話,路徑壓縮就要改一下。
  • 我們可以求個後綴異或和。
  • 舉例來說,對於1234xy 表示y爲x的父親),我們要將1路徑壓縮,即將原樹變成14,24,34 的形態。若1與2反向,2與3同向,3與4反向,我們倒着來做,求一波後綴和,於是可知3與4反向,2與4反向,1與4同向。

  • 還有連邊的時候也要有所修改。
  • 對於xy ,我們都是讓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(mlog2n+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(mlog2n+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);
}
發佈了114 篇原創文章 · 獲贊 52 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章