最長異或路徑(Trie,貪心)
阿新 • • 發佈:2019-01-30
cpp ref 有感 amp 異或和 ans 出發 數值 The 分析:設dis[x]表示根節點到x的路徑上所有邊權的異或和,則有dis[x]=dis[father(x)]^w[x,father(x)],看到這個式子,
傳送門
題意:給定一棵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,貪心)