1. 程式人生 > >最長異或路徑(Trie,貪心)

最長異或路徑(Trie,貪心)

cpp ref 有感 amp 異或和 ans 出發 數值 The

傳送門

題意:給定一棵n個點的帶權樹,結點下標從1開始到N.求樹上最長的異或路徑.異或路徑指的是兩個結點之間的路徑上的所有邊權的異或值的和.

分析:設dis[x]表示根節點到x的路徑上所有邊權的異或和,則有dis[x]=dis[father(x)]^w[x,father(x)],看到這個式子,有沒有感到分外親切?我們可以對樹進行DFS,預處理出所有的dis[x].

根據結論:樹上x到y的路徑上所有邊權的異或和就等於dis[x]^dis[y].證明:若x和y分別在根節點的兩棵子樹上,則它們各自到根節點的路徑沒有重合部分,上面結論顯然成立.若x和y在根節點的同一棵子樹上,則它們到各自根節點的路徑會有重合部分,但因為a^a=0,所以重合部分恰好抵消,綜上結論成立,證畢!

於是題目就變成了在dis[1]~dis[n]這N個數中選出兩個,使它們異或的結果最大.

於是我們可以把dis數組中的每一個元素(整數)看作長度為32的二進制01串(數值較小時,前面補0即可),然後把它們都插入一棵Trie樹中.

接下來對於dis數組的每一個元素,進行一次類似於在Trie中查詢的操作,從根節點出發,每一步都盡量(盡量!!!)沿著與之當前位相反的字符指針向下訪問(貪心策略,根據異或運算"相同得0,不同得1"的運算法則得來).

int n,tot,ans;
int dis[100005],ch[3000005][2];
int cnt,head[100005],nxt[200005];
int to[200005],w[200005];
void add(int a,int b,int c){
    nxt[++cnt]=head[a];
    head[a]=cnt;
    to[cnt]=b;
    w[cnt]=c;
}//還沒忘記鏈式前向星...
void dfs(int x,int father){
    for(int i=head[x];i;i=nxt[i]){
        int y=to[i];
        if(y==father)continue;//這裏註意一下
        dis[y]=dis[x]^w[i];
        dfs(y,x);
    }
}
void insert(int x){//Trie樹插入操作的模板
    int u=0;
    for(int i=31;i>=0;i--){
        int c=(x&(1<<i))>>i;
//1<<i,即2^i;
//x&(1<<i)得到x的二進制數從低到高的第i位是1還是0
//如果是0,則c等於0;如果是1,則c=(2^i)>>i=1
        if(!ch[u][c])ch[u][c]=++tot;
        u=ch[u][c];
    }
}
int query(int x){
    int u=0,ans=0;
    for(int i=31;i>=0;i--){
        int c=(x&(1<<i))>>i;
        if(ch[u][c^1]){
c要麽0要麽1,0^1=1,1^0=0,所以c^1相當於!c
            ans+=(1<<i);
//如果走了與當前位相反的字符指針,則對答案產生貢獻
            u=ch[u][c^1];
        }
        else u=ch[u][c];
    }
    return ans;
}
int main(){
    n=read();//n個節點
    for(int i=1;i<=n-1;i++){
        int a=read(),b=read(),c=read();
        add(a,b,c);add(b,a,c);
    }//建圖
    dfs(1,0);//DFS預處理出dis數組
    for(int i=1;i<=n;i++)
        insert(dis[i]);
//將dis數組插入Trie樹
    for(int i=1;i<=n;i++)
        ans=max(ans,query(dis[i]));
    printf("%d\n",ans);
    return 0;
}

最長異或路徑(Trie,貪心)