字典树模板
//前置条件
const int N=1e7+10;
int sz=1; //节点的编号
//建树
struct Trie {
int ch[26]; //储存字母用 26,数字用 10,二进制用 2
bool flag; //记录接收字符串是否到末尾
//当只有一棵树时不用执行下面操作
void clear(){memset(ch,0,sizeof(ch));flag=false;}
}tree[N];
//插入
void insert(string s)
{
int u=0,c;
for(int i=0;i<s.length();i++)
{
c=s[i]-'a'; //以数字的方式储存,或者减去 '0'
if(!tree[u].ch[c]) tree[u].ch[c]=sz++; //连续的顺序编号
u=tree[u].ch[c]; //结点连接的子节点,用于储存单独的字符串
}
tree[u].flag=true; //接收字符串的末尾
}
//查询
int find(string s)
{
int u=0,c;
for(int i=0;i<s.length();i++)
{
c=s[i]-'a';
if(!tree[u].ch[c]) return 0; //没有接收到这个字符串
u=tree[u].ch[c];
}
//若不是接收字符串的末尾
if(tree[u].flag==false) return 0;
/*
通常这里会有其他的判断条件,根据题目来补充
*/
}
懒得重新画图,干脆直接放草稿上来
解析
字典树也称为前缀树,常用功能有插入和查询
tree[i].ch[j]=k
i
i
i 代表结点编号(比如根节点是 0,结点 a 是 1,结点 c 是 5)
j
j
j 代表字母编号(比如 ‘a’ 对应 0,‘b’ 对应 1,‘c’ 对应 2)
k
k
k 代表节点编号(比如每个节点都有对应编号,按照 1234 的顺序)
于是他错误的点名开始了
#include <bits/stdc++.h>
using namespace std;
const int N=1e7+10;
int sz=1;
struct Trie {
int ch[26],cnt; //cnt 用于记录接收字符串出现的次数,初始为 0,重复出现则 cnt++
bool flag;
}tree[N];
void insert(string s)
{
int u=0,c;
for(int i=0;i<s.length();i++)
{
c=s[i]-'a';
if(!tree[u].ch[c]) tree[u].ch[c]=sz++;
u=tree[u].ch[c];
}
tree[u].flag=true;
}
int find(string s) //第一次出现:1 多次出现:2 无此单词:3
{
int u=0,c;
for(int i=0;i<s.length();i++)
{
c=s[i]-'a';
if(!tree[u].ch[c]) return 3;
u=tree[u].ch[c];
}
if(tree[u].flag==false) return 3;
if(tree[u].cnt==0) {
tree[u].cnt++;
return 1;
}
return 2;
}
int main()
{
int n;cin>>n;
string str;
while(n--)
{
cin>>str;
insert(str);
}
cin>>n;
while(n--)
{
cin>>str;
int ans=find(str);
if(ans==1) cout<<"OK"<<endl;
else if(ans==2) cout<<"REPEAT"<<endl;
else cout<<"WRONG"<<endl;
}
return 0;
}
思路
样例的草稿也就是上面的图
注意 find()
函数是在接收字符串的末尾,才判断是否重复出现过(cnt++)
Phone List
#include <bits/stdc++.h>
using namespace std;
const int N=1e7+10;
string S[N];
int sz=1;
struct Trie {
int ch[10],sum; //sum 用于记录结点连接的子节点的个数
bool flag;
void clear(){memset(ch,0,sizeof(ch));flag=false;sum=0;}
}tree[N];
void insert(string s)
{
int u=0,c;
for(int i=0;i<s.length();i++)
{
c=s[i]-'0'; //记得这里的字符是数字,因此减去 '0'
if(!tree[u].ch[c]) tree[u].ch[c]=sz++;
tree[u].sum++; //子节点的个数加 1
u=tree[u].ch[c];
}
tree[u].flag=true;
}
int find(string s)
{
int u=0,c;
for(int i=0;i<s.length();i++)
{
c=s[i]-'0';
if(!tree[u].ch[c]) return 0;
u=tree[u].ch[c];
}
if(tree[u].sum>0) return 1; //该结点有连接的节点,说明该字符串是连某个字符串的前缀
return 0;
}
int main()
{
int n;cin>>n;
while(n--)
{
for(int i=0;i<1e5;i++) tree[i].clear(); //清空字典树 重新建树
int k;cin>>k;
string str;
int ans=0;
for(int i=0;i<k;i++)
{
cin>>S[i];
insert(S[i]);
}
for(int i=0;i<k;i++)
{
ans=find(S[i]);
if(ans==1) {
cout<<"NO"<<endl;
break;
}
}
if(ans==0) cout<<"YES"<<endl;
}
return 0;
}
思路
小坑是有前缀则输出 NO,没有前缀则输出 YES
注意:tree[u].sum++
在 u=tree[u].ch[c]
的前面,因为判断的是字符串的最后一位(可以画图理解)
The XOR Largest Pair
#include <bits/stdc++.h>
using namespace std;
const int N=1e7+10;
int a[N],sz=1;
struct Trie {
int ch[2];
}tree[N];
void insert(string s)
{
int u=0,c;
for(int i=0;i<s.length();i++)
{
c=s[i]-'0';
if(!tree[u].ch[c]) tree[u].ch[c]=sz++;
u=tree[u].ch[c];
}
}
int find(string s)
{
int u=0,c;
int ans=0,num=pow(2,30);
for(int i=0;i<s.length();i++)
{
c=(s[i]-'0')^1; //要找异或结果是 1 的数字,可以和 1 异或得到该数字
if(tree[u].ch[c]) ans+=num; //加上对应的十进制位数字
else c^=1; //否则没有该数字,则是只有对应的数字
u=tree[u].ch[c];
num/=2;
}
return ans;
}
string transfer(int num) //转为二进制字符
{
string s="";
while(num!=0) {
s+=((num%2)+'0');
num>>=1;
}
while(s.length()!=31) s+='0';
reverse(s.begin(),s.end());
return s;
}
int main()
{
int n;cin>>n;
for(int i=0;i<n;i++)
{
cin>>a[i];
string str=transfer(a[i]);
insert(str);
}
int mm=0;
for(int i=0;i<n;i++)
{
string str=transfer(a[i]);
mm=max(mm,find(str));
}
cout<<mm<<endl;
return 0;
}
懒画,放稿
思路
首先骂一下本题的范围,N 的取值高达 1e7,我找了半天 bug 才找出来(恼)
本来应该 num = pow(2,31)
,但这样 num 越界(int 的范围是 231 - 1),所以 num = pow(2,30)
,那么 transfer()
函数就应该改为 cnt.length()!=31
[USACO08DEC]Secret Message G
#include <bits/stdc++.h>
using namespace std;
const int N=1e7+10;
int sz=1;
struct Trie {
int ch[2],sum;
int flag; //注意这里是 int 类型!
}trie[N];
void insert(string s)
{
int u=0,c;
for(int i=0;i<s.length();i++)
{
c=s[i]-'0';
if(!trie[u].ch[c]) trie[u].ch[c]=sz++;
u=trie[u].ch[c];
trie[u].sum++; //加的是节点自身
}
trie[u].flag++; //注意有重复信息
}
int find(string s)
{
int u=0,c,ans=0;
for(int i=0;i<s.length();i++)
{
c=s[i]-'0';
if(!trie[u].ch[c]) return ans;
u=trie[u].ch[c];
if(trie[u].flag>0) ans+=trie[u].flag;
}
ans-=trie[u].flag; //去重
ans+=trie[u].sum;
return ans;
}
int main()
{
int m,n;cin>>m>>n;
char k;
string str;
int tmp,all;
for(int i=0;i<m;i++)
{
str="";
cin>>tmp;
while(tmp--) {
cin>>k;str+=k;
}
insert(str);
}
for(int i=0;i<n;i++)
{
str="";
cin>>tmp;
while(tmp--) {
cin>>k;str+=k;
}
all=find(str);
cout<<all<<endl;
}
return 0;
}
思路
题目有些难读懂,大体意思就是:给你 n 个信息和 m 个暗号,要你求出对于对于每条暗号,有多少信息和这条暗号有着相同的前缀(这个前缀就是暗号和信息长度较小的那个)
方法就是以 n 个字符串建字典树,用 m 个暗号匹配查询,当到达某个信息的末尾则 ans++
(ans是相同前缀数),到达暗号的末尾则 ans+=sum
(sum是含暗号前缀的剩下信息总数),注意要减去重复的信息数量
注意:tree[u].sum++
在 u=tree[u].ch[c]
的后面,因为加的是字符串的字符自身(可以画图理解)
【重做】The XOR Largest Pair
#include <bits/stdc++.h>
using namespace std;
const int N=1e7+10; //比 31*1e5大就行了
int sz=1;
int tmp[32];
struct Trie {
int ch[2];
}tree[N];
void insert(int x)
{
int u=0,c=0;
memset(tmp,0,sizeof(tmp));
while(x)
{
tmp[c++]=x&1; //从最后一位开始记录
x>>=1;
}
for(int j=31;j>=0;j--) //最大有 32位,所以下标从 31开始
{
if(!tree[u].ch[tmp[j]]) tree[u].ch[tmp[j]]=sz++;
u=tree[u].ch[tmp[j]];
}
}
long long find(int x)
{
int u=0,c=0;
memset(tmp,0,sizeof(tmp));
while(x)
{
tmp[c++]=x&1; //从最后一位开始记录
x>>=1;
}
long long ans=0;
for(int j=31;j>=0;j--)
{
if(!tree[u].ch[1-tmp[j]]) u=tree[u].ch[tmp[j]];
else
{
u=tree[u].ch[1-tmp[j]];
ans+=(1<<j);
}
}
return ans;
}
int main()
{
int n;cin>>n;
int x;
long long res=0;
while(n--)
{
cin>>x;
insert(x);
res=max(res,find(x));
}
cout<<res<<endl;
return 0;
}
思路
01
01
01字典树的模板,两个函数都是直接传入的参数是
i
n
t
int
int 而不是
s
t
r
i
n
g
string
string,这样会快一点(也更方便一点),还有加了注释的地方需要理解一下,补充一点:
2
0
2^0
20 有
1
1
1 位、
2
1
2^1
21 有
2
2
2 位
.
.
.
...
...
2
31
2^{31}
231 有
32
32
32 位
Nikitosh 和异或(未)
#include<bits/stdc++.h>
using namespace std;
const int N=12e6+10; //Ai小于 2的 30次方,整体小于 30*4e5
int tmp[32],sz=1;
int s[N];
long long l[N],r[N];
struct Trie {
int ch[2];
void clear(){memset(ch,0,sizeof(ch));}
}tree[N];
void insert(int x)
{
int u=0,c=0;
memset(tmp,0,sizeof(tmp));
while(x)
{
tmp[c++]=x&1;
x>>=1;
}
for(int j=31;j>=0;j--)
{
if(!tree[u].ch[tmp[j]]) tree[u].ch[tmp[j]]=sz++;
u=tree[u].ch[tmp[j]];
}
}
long long find(int x)
{
int u=0;
long long ans=0;
// 因为 insert和 find的是同一个数字 因此不用清空
/*
memset(tmp,0,sizeof(tmp));
while(x)
{
tmp[c++]=x&1;
x>>=1;
}
*/
for(int j=31;j>=0;j--)
{
if(!tree[u].ch[1-tmp[j]]) u=tree[u].ch[tmp[j]];
else
{
ans+=(1<<j);
u=tree[u].ch[1-tmp[j]];
}
}
return ans;
}
int main()
{
int n;cin>>n;
int x=0;
long long res=0;
for(int i=1;i<=n;i++) cin>>s[i];
for(int i=1;i<=n;i++)
{
x^=s[i];
insert(x);
l[i]=max(l[i-1],find(x));
}
for(int i=0;i<N;i++) tree[i].clear();
x=0;
for(int i=n;i>=1;i--)
{
x^=s[i];
insert(x);
r[i]=max(r[i+1],find(x));
}
for(int i=1;i<n;i++)
{
res=max(res,l[i]+r[i+1]);
}
cout<<res<<endl;
return 0;
}
过了样例,又满分AC的大佬代码
#include <cstdio>
#include <cstring>
#include <string>
using namespace std;
int pot, ispn;
char ch, sk[100];
typedef long long ll;
typedef unsigned long long ull;
#define il inline
#define ENDL putchar('\n')
#define PUT(ch) putchar(ch)
#define GET(ch) ch = getchar()
#define isd(ch) (ch >= 48 && ch <= 57)
#define mst(a, b) memset(a, b, sizeof(a))
#define rep(a, b, c) for (register int a = b; a <= c; ++ a)
#define drep(a, b, c) for (register int a = b; a >= c; -- a)
template <typename T>
il T Abs(T a) {
return a < 0 ? -a : a;
}
template <typename T>
il T Min(T a, T b) {
return a < b ? a : b;
}
template <typename T>
il T Max(T a, T b) {
return a > b ? a : b;
}
template <typename T>
il void Swap(T &a, T &b) {
a ^= b ^= a ^= b;
}
template <typename T>
il void Minn(T &a, T b) {
a = a < b ? a : b;
}
template <typename T>
il void Maxx(T &a, T b) {
a = a > b ? a : b;
}
template <typename T>
il void read(T &x) {
x = 0;
ispn = 0;
GET(ch);
while (!isd(ch)) {
if (ch == '.')
ispn = 1;
GET(ch);
}
while (isd(ch))
x = (x << 1) + (x << 3) + (ch ^ 48), GET(ch);
if (ispn)
x = -x;
}
template <typename T>
il void write(T x) {
pot = 0;
if (x < 0)
x = -x, PUT('-');
do {
sk[++ pot] = x % 10 + 48;
} while (x /= 10);
while (pot)
PUT(sk[pot --]);
}
const int N = 400010;
int n, a[N], c[N][2];
ll ans, lx[N], rx[N], sss[N];
int u, sum;
il void insert(int x) {
drep(i, 30, 0) {
u = ((x >> i) & 1);
if (!c[i][u])
c[i][u] = 1;
}
}
il ll fnd(int x) {
sum = 0;
drep(i, 30, 0) {
u = ((x >> i) & 1);
if (c[i][u ^ 1])
sum += (1 << i);
}
return sum;
}
int main() {
read(n);
insert(0);
rep(i, 1, n) read(a[i]);
rep(i, 1, n) sss[i] ^= a[i];
rep(i, 1, n) {
lx[i] = Max(lx[i - 1], fnd(sss[i]));
insert(sss[i]);
}
mst(c, 0), mst(sss, 0);
insert(0);
drep(i, n, 1) sss[i] ^= a[i];
drep(i, n, 1) {
rx[i] = Max(rx[i + 1], fnd(sss[i]));
insert(sss[i]);
}
rep(i, 1, n - 1) Maxx(ans, lx[i] + rx[i + 1]);
write(ans);
return 0;
}
思路
样例没过,但AC了,死活看不懂,看了大佬的代码,发现这就是
A
C
M
e
r
ACMer
ACMer 和 我这个渣渣的差距啊…
参考文章
[SCOI2016]背单词
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5.1e5+10; //N为字符长度总和
ll ans; //ans要用 long long
int sz=1,sum[N],last[N],cnt=1;
vector<int> g[N];
struct Trie {
int ch[26];
bool flag;
}tree[N];
void insert(string s)
{
int u=0,c;
for(int i=0;i<s.length();i++)
{
int c=s[i]-'a';
if(!tree[u].ch[c]) tree[u].ch[c]=sz++;
u=tree[u].ch[c];
}
tree[u].flag=true;
}
//虽然但是,这种递归方法太难理解了
void rebuild(int x)
{
if(x&&tree[x].flag)
{
g[last[x]].push_back(x); //g[x][]存重构树中点 x的儿子们
last[x]=x; //last[x]是原树里 x节点上方离它最近的红点(包括自己)
}
for(int i=0;i<26;i++)
{
if(tree[x].ch[i])
{
last[tree[x].ch[i]]=last[x];
rebuild(tree[x].ch[i]);
}
}
}
bool cmp(const int &x,const int &y) {
return sum[x]<sum[y];
}
//深搜重排 trie树
void dfs(int x)
{
sum[x]=1; //sum[x]是以 x为根的子树的大小
for(int i=0;i<g[x].size();i++)
{
dfs(g[x][i]);
sum[x]+=sum[g[x][i]];
}
sort(g[x].begin(),g[x].end(),cmp);
}
//深搜遍历 trie树统计答案
void finally(int x)
{
int index=cnt++;
for(int i=0;i<g[x].size();i++)
{
ans+=cnt-index; //inedx是前缀位置,cnt是节点自己的位置
finally(g[x][i]);
}
}
int main()
{
int n;cin>>n;
string str;
while(n--)
{
cin>>str;
reverse(str.begin(),str.end()); //反转后 按照前缀处理即可
insert(str);
}
rebuild(0);
dfs(0);
finally(0);
cout<<ans<<endl;
return 0;
}
思路:
思路来源
这种题相比思路,我更多的学到的是代码的细节,每个函数所对应的作用在别的题目中都有可能会用到,因此多看看函数是如何实现的吧(不行就硬背 ),然后对于整体的思路我的理解如下(配合上面的草稿纸观看):
① 重建后的树就是上图的树,之所以
I
I
I 在
A
A
A 的左边是因为子树
A
A
A 所含有的子节点是最多的
② 最终排序是按照层序遍历,即从左到右,将每个节点的值减去其结点的值(根结点值为
0
0
0),全部加起来即最终结果,经草稿纸验算,这样的结果是最小的,毕竟很容易想到小的数要排序在最上面
[HNOI2004]L语言
#include<bits/stdc++.h>
using namespace std;
const int N=2e6+10;
int sz=1,f[N];
char s[N];
map<string,int> mmp; //也能记录 char[]
inline int read() {
int x = 0, neg = 1; char op = getchar();
while (!isdigit(op)) { if (op == '-') neg = -1; op = getchar(); }
while (isdigit(op)) { x = 10 * x + op - '0'; op = getchar(); }
return neg * x;
}
inline void print(int x) {
if (x < 0) { putchar('-'); x = -x; }
if (x >= 10) print(x / 10);
putchar(x % 10 + '0');
}
struct Trie {
int ch[26];
bool flag;
}tree[N];
inline void insert()
{
scanf("%s",s+1);
int u=0,c;
for(register int i=strlen(s+1);i>=1;i--)
{
c=s[i]-'a';
if(!tree[u].ch[c]) tree[u].ch[c]=sz++;
u=tree[u].ch[c];
}
tree[u].flag=true;
}
inline void find()
{
scanf("%s",s+1);
if(mmp[s+1])
{
print(mmp[s+1]);
cout<<endl;
return;
}
f[0]=true;//初始化 前 0个字符一定可以匹配
for(register int i=1;i<=strlen(s+1);i++)
{
int u=0,c;
f[i]=false; //先让 f[i]=false 省去了开始的 memset
for(register int j=i;j>=1;j--)
{
c=s[j]-'a';
if(!tree[u].ch[c]) break;
u=tree[u].ch[c];
if(tree[u].flag==true)
{
f[i]|=f[j-1]; //若 f[j-1]是 1,f[i]就是 1,否则 f[i]是 0
if(f[i]) break; //接的上就退出
}
}
}
for(register int i=strlen(s+1);i>=0;i--)
{
if(f[i])
{
mmp[s+1]=i;
print(i);
cout<<endl;
return;
}
}
}
int main()
{
int n,m;
n=read();m=read();
while(n--) insert();
while(m--) find();
return 0;
}
思路
一开始我以为很简单,我的思路就是前几个字符串构建树,后面几个字符串对树进行判断,每到一处能理解的地方就++,最后输出总和,结果我提交
W
A
WA
WA 了几个点,后来我发现这样做错误的地方,就是存在重复的前缀(以下面的输入举例)
2 1
abc
abcd
abcd
本来结果应该是 4,但我原本的代码只会输出 3,因为当判断到 a b c abc abc 能理解就会++并退出,而这并不是最长前缀
改进方法
将原字符串倒着构建树,比如上面那个例子,将
c
b
a
cba
cba 和
d
c
b
a
dcba
dcba 构建树,判断
a
b
c
d
abcd
abcd 时先遍历再倒着来,即先判断
a
a
a,再判断
b
a
ba
ba,再判断
c
b
a
cba
cba,最后判断
d
c
b
a
dcba
dcba,发现能理解,于是就输出 4
但是还有要注意的地方,比如草稿纸下面的例子
a
b
c
d
e
f
j
abcdefj
abcdefj,当判断
a
b
c
d
abcd
abcd 能理解后,再判断
f
j
fj
fj 和
e
f
j
efj
efj 都能理解,那么该取哪个?我们只需要一开始对每个都设置
f
[
i
]
=
f
a
l
s
e
f[i]=false
f[i]=false,当能理解时写下
f
[
i
]
∣
=
f
[
j
−
1
]
f[i]|=f[j-1]
f[i]∣=f[j−1] 即可,就是当前面的字符串(
a
b
c
d
abcd
abcd)也可以理解时,那么就可以将此处
f
[
i
]
f[i]
f[i] 与前面的合并,如果前面的是
t
r
u
e
true
true,那么此处也是
t
r
u
e
true
true,否则是
f
a
l
s
e
false
false(合并不成功)【
∣
|
∣ 是或运算符,两者至少有一个是
t
r
u
e
true
true 则结果为
t
r
u
e
true
true】
再改进
其实上面的代码还是过不了洛谷的评测机,会
T
L
E
TLE
TLE 掉四个点(即使用了
m
a
p
map
map 和 快读快输),这时候我不得不改写字典树成普通的两个分开的数组(毕竟写了这么多题都很熟练了)如下:
struct Trie {
int ch[26];
bool flag;
}tree[N];
改成:
int tree[N][26];
bool flag[N];
最终代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e6+10;
int sz=1,f[N];
char s[N];
map<string,int> mmp;
inline int read() {
int x = 0, neg = 1; char op = getchar();
while (!isdigit(op)) { if (op == '-') neg = -1; op = getchar(); }
while (isdigit(op)) { x = 10 * x + op - '0'; op = getchar(); }
return neg * x;
}
inline void print(int x) {
if (x < 0) { putchar('-'); x = -x; }
if (x >= 10) print(x / 10);
putchar(x % 10 + '0');
}
int tree[N][26];
bool flag[N];
inline void insert()
{
scanf("%s",s+1);
int u=0,c;
int len=strlen(s+1);
for(register int i=len;i>=1;i--)
{
c=s[i]-'a';
if(!tree[u][c]) tree[u][c]=sz++;
u=tree[u][c];
}
flag[u]=true;
}
inline void find()
{
scanf("%s",s+1);
if(mmp[s+1])
{
print(mmp[s+1]);
cout<<endl;
return;
}
f[0]=true;
int len=strlen(s+1);
for(register int i=1;i<=len;i++)
{
int u=0,c;
f[i]=false;
for(register int j=i;j>=1;j--)
{
c=s[j]-'a';
if(!tree[u][c]) break;
u=tree[u][c];
if(flag[u]==true)
{
f[i]|=f[j-1];
if(f[i]) break;
}
}
}
for(register int i=len;i>=0;i--)
{
if(f[i])
{
mmp[s+1]=i;
print(i);
cout<<endl;
return;
}
}
}
int main()
{
int n,m;
n=read();m=read();
while(n--) insert();
while(m--) find();
return 0;
}
最长异或路径(未)
#include<bits/stdc++.h>
#define IL inline
#define RI register int
#define maxn 100008
int trie[maxn*31][2],xo[maxn],ans,rt;
int val[maxn],n,head[maxn],tot;
struct code{int u,v,w;}edge[maxn<<1];
IL void read(int &x){
int f=1;x=0;char s=getchar();
while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
while(s<='9'&&s>='0'){x=x*10+s-'0';s=getchar();}
x*=f;
}
IL void add(int x,int y,int z)
{
edge[++tot].u=head[x];
edge[tot].v=y;
edge[tot].w=z;
head[x]=tot;
edge[++tot].u=head[y];
edge[tot].v=x;
edge[tot].w=z;
head[y]=tot;
}
IL void build_trie(int x,int rt)
{
for(RI i=1<<30;i;i>>=1)
{
bool c=x&i;
if(!trie[rt][c])trie[rt][c]=++tot;
rt=trie[rt][c];
}
}
IL int query(int x,int rt)
{
int ans=0;
for(RI i=1<<30;i;i>>=1)
{
bool c=x&i;
if(trie[rt][c^1])ans+=i,rt=trie[rt][c^1];
else rt=trie[rt][c];
}
return ans;
}
IL void dfs(int u,int fa)
{
for(RI i=head[u];i;i=edge[i].u)
{
if(edge[i].v!=fa)
{
xo[edge[i].v]=xo[u]^edge[i].w;
dfs(edge[i].v,u);
}
}
}
int main()
{
read(n);
for(RI i=1,u,v,w;i<n;i++)read(u),read(v),read(w),add(u,v,w);
dfs(1,0);
for(RI i=1;i<=n;i++)build_trie(xo[i],rt);
for(RI i=1;i<=n;i++)ans=std::max(ans,query(xo[i],rt));
printf("%d",ans);
}
有用到 d f s dfs dfs 优化的 D i j k s t r a Dijkstra Dijkstra,我也忘得差不多了,等我复习一遍后再回来看