1. 程式人生 > >牛客國慶集訓派對Day1 D-Love Live! (啟發式合併+01字典樹)

牛客國慶集訓派對Day1 D-Love Live! (啟發式合併+01字典樹)

題目描述

因為招生辦的招生政策變化,Otonokizaka Academy的ACM-ICPC team面臨廢隊危機。Honoka Kosaka,Kotori Minami,Umi Sonoda等人決定成為偶像來吸引更多的學生參加ICPC。 Honoka決定選取一些動作來編舞。我們把所有可以選擇的動作用一棵 n 個點的樹上的邊表示,其中樹的定義是無環的無向聯通圖。樹上的每條邊有一個邊權 w(1 ≤ w < n),且所有邊的邊權是互不相同的。如果兩條邊沒有公共節點,就代表它們對應的動作差異很大,沒有辦法連續做出。又因為每個動作只能在

舞蹈中出現一次,所以能組成一支舞蹈的一套動作一定對應著樹上的一條簡單路徑。

此外,舞蹈的優美度定義為其路徑上所有邊的邊權異或和,難度定義為路徑上所有邊的邊權最大值。 Honoka想知道對於[1, n) 的每種難度,最優美的舞蹈的優美度是多少。

輸入描述:

輸入第一行一個正整數 n(2 ≤ n ≤ 105)。
接下來 n-1 行,每行三個正整數 u,v,w(1≤ u,v ≤ n, 1≤ w < n) 表示點 u 和點 v 之間有一條邊權為 w 的邊。
保證輸入的圖可以構成一棵樹,且所有邊的邊權互不相同。

輸出描述:

一行 n-1 個整數,第 i 個數表示所有難度為 i 的舞蹈中最大的優美度。

示例1

輸入

複製

7
1 2 4
1 3 3
2 4 1
2 5 2
3 6 5
3 7 6

輸出

複製

1 3 3 7 6 6

解題思路:異或相關肯定想到字典樹。對於每一條邊,我們只關心周圍比他小的邊。考慮暴力,對於每一條邊,我們把周圍比他小的邊都單獨拿出來重新建樹,重新建樹之後怎麼確定最大異或和呢,答案肯定與邊兩邊的子樹有關。我們記錄下邊兩邊的子樹的所有節點到他的根(根就是兩個頂點)的異或和,然後就可以通過兩個for暴力計算出答案了。這一步可以優化一下,我們先預處理出一邊的點到他的根異或和,然後插入到一顆字典樹裡,然後另一邊直接在字典樹中查詢即可,這樣複雜度變成了一個for了。毫無疑問我們每次遍歷,肯定是遍歷小的那個子樹好,所以這裡啟發式一下。但是我們重新建樹需要一個dfs,因此總的複雜度還是不能接受。考慮優化,我們每次都從最小的邊重新建樹,這樣下一個大小的邊如果跟之前的樹聯通,直接插入邊即可。這樣一共只會插入邊N次。然後用並查集維護下每一條邊兩邊的根即可。

這裡用遍歷一個G即可模擬dfs操作,學到了。

#include<iostream>
#include<algorithm>
#include<math.h>
#include<queue>
#include<string>
#include<vector>
#include<bitset>
using namespace std;
const int MAXN=100006;

struct edge{
    int u,v,w;
}e[MAXN];
bool cmp(const edge &a,const edge &b){
    return a.w<b.w;
}
vector<int> G[MAXN];//記錄一個點會深搜到的點,按照dfs序排,所以遍歷一遍等於深搜了一次。
int sz[MAXN];//記錄子樹大小,用於啟發式
int V[MAXN];//每一個點到他的並查集的根的異或和(用於優化深搜)

//並查集
int fa[MAXN];
int find(int x){
    return x==fa[x]?x:fa[x]=find(fa[x]);
}

/*******01字典樹部分********/
int ch[MAXN*100][2];
int tot=0;
int root[MAXN];//新建N個字典樹
void insert(int u,int x)//向根為u的字典樹插入x
{
    for(int i=18;i>=0;i--)
    {
        int id=(x>>i)&1;
        if(!ch[u][id])
            ch[u][id]=++tot;
        u=ch[u][id];
    }
}
int query(int u,int x)//查詢根為u的字典樹與x的異或最大值
{
    int res=0;
    for(int i=18;i>=0;i--)
    {
        int id=(x>>i)&1;
        if(ch[u][id^1])
        {
            u=ch[u][id^1];
            res|=(1<<i);
        }
        else
            u=ch[u][id];
    }
    return res;
}
/*****01字典樹部分結束**********/


int main(){

    int N;
    scanf("%d",&N);
    for(int i=1;i<N;i++)
        scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
    sort(e+1,e+N,cmp);

    for(int i=1;i<=N;i++){
        fa[i]=i;
        sz[i]=1;
        G[i].emplace_back(i);
        root[i]=++tot;
        insert(root[i],0);//01字典樹的坑,必須要插入一個0
    }


    for(int j=1;j<N;j++){
        int x=e[j].u;
        int y=e[j].v;
        int w=e[j].w;

        int fx=find(x);
        int fy=find(y);

        //啟發式
        if(sz[fx]>sz[fy]){
            swap(x,y);
            swap(fx,fy);
        }
        w=V[x]^V[y]^w;//由於使用了並查集合並,所以這裡有個坑。但直接使用深搜沒有這個坑

        int ans=0;

        for(int i=0;i<G[fx].size();i++)//暴力深搜小的那個,查詢跟大的那個的異或和的最大值
            ans=max(ans,query(root[fy],V[G[fx][i]]^w));
        
        for(int i=0;i<G[fx].size();i++)//更新大的那個
        {
            G[fy].emplace_back(G[fx][i]);
            V[G[fx][i]]^=w;
            insert(root[fy],V[G[fx][i]]);
        }
        fa[fx]=fy;
        sz[fy]+=sz[fx];

        printf("%d ",ans);
    }

    return 0;
}