输入一棵 个点的 (即每条边有边权的树),求树上最长异或路径。
记 为边权,则有 。 ,且所有的点从 到 编号。
像这种,求树上路径的异或和或长度和等等可以利用前缀和算法维护的信息时,我们一般都要用到树上前缀和算法。
具体地,我们可以记 表示从根(无根树吗,随便找个点当根得了,我们就当它是 吧)到 的路径异或和。根据异或的性质,两点 间路径的异或和即 异或 。
于是,我们可以把所有的
都求出来,然后把它们塞进一棵Trie
内,问题就转化为了求两两数之间的异或最大值。这是一个Trie
的简单应用题,这里不在介绍。
const int N=1e5+100;
struct Tire{
int ch[N*30][2],tot;//千万别只开ch[N][2]
void init_trie(){
memset(ch,-1,sizeof(ch));
tot=0;
}
void insert(int x){
register int u=0;
for(int i=30;i>=0;i--){
int c=x&(1<<i)?1:0;
if (ch[u][c]==-1)
ch[u][c]=++tot;
u=ch[u][c];
}
}
int query(int x){
register int u=0,ans=0;
for(int i=30;i>=0;i--){
int c=x&(1<<i)?1:0;
if (ch[u][1-c]!=-1){
ans+=(1<<i);
u=ch[u][1-c];
}
else u=ch[u][c];
}
return ans;
}
}trie;//封装一个Trie的模板
struct node{
int next,to,value;
}e[N<<1];int h[N],tot;
inline void add(int a,int b,int w){
e[++tot]=(node){h[a],b,w};h[a]=tot;
}//链式前向星存图
int d[N],ans,n;
void dfs_init(int u,int fa){
for(int i=h[u];i;i=e[i].next){
register int to=e[i].to;
if (to==fa) continue;
d[to]=d[u]^e[i].value;
dfs_init(to,u);
}
}//求前缀异或和
int main(){
n=read();ans=-1;
trie.init_trie();//记得要初始化
for(int i=1;i<n;i++){
int u=read(),v=read(),w=read();
add(u,v,w);add(v,u,w);
}//因为是无根树,所以最好建立无向图
dfs_init(1,-1);//别忘了调用
for(int i=1;i<=n;i++){
trie.insert(d[i]);
ans=max(ans,trie.query(d[i]));
}//先后顺序无所谓
printf("%d",ans);
return 0;
}
给定
,求(
表示求余):
。
首先,我们来看看计算机是怎么计算求余的结果的。假设现在要求 ,结果为 ,我们的计算机会这么算:
这给我们什么启示?我们发现 变化的特别慢,至多会变化 次, ,而且所有令 不变的 是连续的,我们完全可以考虑所有的 的值,然后统计答案。
我们发现代码非常的简单,但也非常的不好理解,尤其是那个for循环
。这里,我们简单讲讲。
它的作用是求出最大的
,使得
,根据它的特有的性质,我们可以知道对于任意
,有
。
这个减号是什么意思?其实是这样的,因为
,所以我们可以把所求式转化为:
因为上文已经说道 都一样,所以原式可以继续变形为:
这样就不难理解为什么我们有这么一段代码了吧。
Q:实在不能理解怎么办?
A:自己举个例子,模拟一下。例子可以很好的帮助我们理解。
Q:数论题自己想不到,但一看题解就懂是怎么回事?
A:您这种情况跟笔者一模一样,笔者只能说:多刷题吧!
Q:您这些思路怎么来的?我怎么一开始没想到?
A:您好,我一开始也没想到,思路全部来自题解或老师的讲解。