CF466E Information Graph 題解

Alexxtl發表於2024-07-19

題目連結

Luogu

Codeforces

題意簡述

某公司中有 \(n\) 名員工。為方便起見,將這些員工從 1 至 \(n\) 編號。起初,員工之間相互獨立。接下來,會有以下 \(m\) 次操作:

  1. 員工 \(y\) 成為員工 \(x\) 的上司。保證此前 \(x\) 沒有上司

  2. 員工 \(x\) 拿到一份檔案並簽字,隨後交給他的上司。他的上司簽字後,再交給更上一級。依此類推,直到檔案傳遞到的那個人沒有上司為止。

  3. 詢問員工 \(x\) 是否在第 \(i\) 件檔案上籤過字。檔案編號為上一件檔案的編號再加 1,第一件檔案的編號為 1。如果是,輸出 YES,否則輸出 NO

解法說明

顯然,我們可以將員工之間的關係看作森林,將每個員工看作一個節點,其與上司的關係看作一條邊。之所以不是一棵樹,是因為在 \(m\) 次操作中,有些人可能並沒有被指定上司,所以員工之間的關係很可能並不是一棵樹而是森林

透過觀察題面可以發現,一個員工在成為另一個員工的上司後,就不會再有更改了。由於線上操作過於麻煩,我們可以考慮離線

具體離線方法如下:

  • 對於操作 1,直接連邊即可,不過這裡還要線上維護一個並查集

  • 對於操作 2,分別記下第一個和最後一個對檔案簽字的員工,後者就是前者所在的連通塊的根,利用並查集查詢;

  • 對於操作 3,分別記下員工編號及檔案編號,離線回答

接下來分析如何回答詢問。可以發現,如果詢問的員工 \(x\) 在 最開始看到檔案 \(i\) 的員工與最後看到檔案 \(i\) 的員工之間的鏈上,那麼 \(x\) 就看過檔案。所以,問題就被轉化為了判斷 \(x\) 是否在這條鏈上

考慮如何判斷。

\(st\) 為鏈的起始點,\(ed\) 為截止點,可推得如 \(x\) 在鏈上,則 \(\text{lca}(x,st) = x\)\(\text{lca}(x,ed) = ed\),維護一個 LCA 即可求解。我這裡用的是樹剖求 LCA,倍增也可以。

還有一些細節需要注意。由於員工之間的關係是森林而非一棵樹,所以我們在預處理樹剖時應列舉每個點,如果該點是其所屬的連通塊的根,就對其進行一次預處理,且回答詢問時應首先判斷 \(x\)\(st\)\(ed\) 是否在同一連通塊內,如果不在直接輸出 NO,否則再執行下一步操作。

剩餘細節詳見下面程式碼中的註釋。

透過程式碼

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define PII pair<int,int>
#define mp make_pair
const int N=1e5+10;

namespace IO{
    //快讀 
    inline int read(){
        int x=0,f=1;
        char ch=getchar();
        while(ch<'0'||ch>'9'){
            if(ch=='-'){
                f=-1;
            }
            ch=getchar();
        }
        while(ch>='0'&&ch<='9'){
            x=(x<<1)+(x<<3)+(ch^48);
            ch=getchar();
        }
        return x*f;
    }
    
    //快寫 
    inline void write(int x){
        if(x<0){
            putchar('-');
            x=-x;
        }
        if(x>9){
            write(x/10);
        }
        putchar(x%10+'0');
    }
} 

using namespace IO;

namespace code{
    //鏈式前向星存圖 
    int head[N],tot;
    
    struct node{
        int ver,next;
    }t[N<<1];
    
    void add(int x,int y){
        t[++tot].ver=y,t[tot].next=head[x],head[x]=tot;
    }
    
    //並查集
    int fa[N];
    
    int getfa(int x){
        if(fa[x]==x){
            return x;
        }
        return fa[x]=getfa(fa[x]);
    } 
    
    //樹鏈剖分 
    int fat[N],size[N],son[N],deep[N],top[N];
    
    void dfs1(int x){
        size[x]=1;
        int maxson=-1;
        for(int i=head[x];i;i=t[i].next){
            int y=t[i].ver;
            if(y==fat[x]){
                continue;
            } 
            fat[y]=x;
            deep[y]=deep[x]+1;
            dfs1(y);
            if(size[y]>maxson){
                maxson=size[y];
                son[x]=y;
            }
            size[x]+=size[y];
        }
    }
    
    void dfs2(int x,int from){
        top[x]=from;
        if(!son[x]){
            return;
        }
        dfs2(son[x],from);
        for(int i=head[x];i;i=t[i].next){
            int y=t[i].ver;
            if(y==son[x]||y==fat[x]){
                continue;
            }
            dfs2(y,y);
        }
    }
    
    //求LCA
    int lca(int x,int y){
        while(top[x]!=top[y]){
            if(deep[top[x]]<deep[top[y]]){
                swap(x,y);
            }
            x=fat[top[x]];
        }
        if(deep[x]<deep[y]){
            return x;
        }
        return y;
    }
    
    //主程式 
    int n,m,f_tot,q_tot;
    PII file[N],query[N];
    
    void solve(){
        n=read(),m=read();
        for(int i=1;i<=n;i++){//並查集預處理 
            fa[i]=i;
        }
        for(int i=1;i<=m;i++){//離線處理 
            int op=read();
            if(op==1){
                int x=read(),y=read();
                add(y,x);//單向邊 
                fa[x]=getfa(y);//線上維護並查集 
            }else if(op==2){
                int x=read();
                file[++f_tot]=mp(x,getfa(x));
            }else{
                int x=read(),y=read();
                query[++q_tot]=mp(x,y);
            }
        }
        for(int i=1;i<=n;i++){//列舉所有點 
            if(getfa(i)==i){//判斷是否為所在連通塊的根 
                deep[i]=1;//樹剖預處理 
                fat[i]=i;
                dfs1(i);
                dfs2(i,i);
            }
        }
        for(int i=1;i<=q_tot;i++){
            int x=query[i].first,y=query[i].second,st=file[y].first,ed=file[y].second;
            if(getfa(x)!=getfa(st)){//是否在同一個連通塊 
                printf("NO\n");
                continue;
            }
            if(lca(x,st)==x&&lca(x,ed)==ed){//判斷 
                printf("YES\n");
            }else{
                printf("NO\n");
            }
        }
    }
}

using namespace code;

signed main(){
    solve();
    return 0;
}

相關文章