链接:https://www.nowcoder.com/acm/contest/201/D
来源:牛客网
题目描述
因为招生办的招生政策变化,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;
}