【01trie】【启发式合并】P6072 『MdOI R1』Path

该博客介绍了一种解决图中两点间无交点路径问题的方法,通过计算每个节点的子树内和子树外的最大异或路径和,然后利用Trie数据结构高效地合并路径信息。作者提供了详细的分析和代码实现,目标是在给定时间内找到两条路径,使得它们异或和最大。
题意

每个点有点权,求两条没有交点的路径,使得两条路径的异或和的和最大

n≤3e4时限:3.5sn \le 3e4 \\ 时限:3.5sn3e43.5s

分析

给每个点求出in[i] out[i]in[i] \ out[i]in[i] out[i]表示子树内的最大异或路径和子树外的最大异或路径

in只需要给树dfs时,给Trie进行启发式合并即可两只log求出

对于求out,我们找出全局最大异或路径A-B的异或和为ans,考虑只有A->rt 和 B->rt 这两条路径上的点的out不是ans

我们要求两条链上的out,依次加入除了子节点子树外的部分到trie即可

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=3e4+5;
int n;
vector <PII> G[maxn];
struct Trie
{
    int ch[2];
    int gs;
}tr[maxn<<7];
int rt[maxn],a[maxn],tot;
int in[maxn],out[maxn];
int A,B,p,q;
void insert(int &p,int val)
{
    if(!p) p=++tot; 
    int now=p;
    tr[now].gs++;
    for(int i=30;i>=0;i--)
    {
        int dig=val>>i&1;
        if(!tr[now].ch[dig]) tr[now].ch[dig]=++tot;
        now=tr[now].ch[dig];
        tr[now].gs++;
    }
}
int query(int now,int val)
{
    int res=0;
    for(int i=30;i>=0;i--)
    {
        int dig=val>>i&1;
        if(tr[tr[now].ch[!dig]].gs) now=tr[now].ch[!dig],res|=1<<i;
        else now=tr[now].ch[dig];
    }
    return res;
}
void merge(int x,int p,int val,int wei,int &v)
{
    if(!x) return;
    if(wei==-1)
    {
        v=max(v,query(rt[p],val));
        insert(rt[p],val);
        return;
    }
    merge(tr[x].ch[0],p,val,wei-1,v);
    merge(tr[x].ch[1],p,val|1<<wei,wei-1,v);
}
int father[maxn],dfn[maxn],dfs_time;
void dfs(int u,int fa)
{
    father[u]=fa; dfn[++dfs_time]=u;
    insert(rt[u],a[u]);
    for(auto to:G[u])
    {
        int v=to.first;
        if(v==fa) continue;
        a[v]=a[u]^to.second;
        dfs(v,u);
        if(tr[rt[v]].gs>tr[rt[u]].gs) swap(rt[u],rt[v]);
        merge(rt[v],u,0,30,in[u]);
        in[u]=max(in[u],in[v]);
    }
}
int tag[maxn],bel[maxn];
vector <int> v[maxn];
void calc(int x)
{
    for(int i=1;i<=tot;i++) tr[i].gs=0;
    for(int i=x;i;i=father[i]) tag[i]=1;
    int now=0;
    for(int k=1,i;k<=n;k++,v[i].clear(),v[bel[i]].push_back(a[i]))
    {
        if(tag[i=dfn[k]]) bel[i]=i;
        else bel[i]=bel[father[i]];
    }
    for(int i,k=1;k<=n;k++)
        if(tag[i=dfn[k]])
        {
            for(int w:v[father[i]])
                now=max(now,query(rt[0],w)),insert(rt[0],w);
            out[i]=max(out[i],now);
            tag[i]=0;
        }
}
int main()
{
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
    scanf("%d",&n);
    int x,y,z;
    for(int i=1;i<n;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        G[x].push_back(make_pair(y,z));
        G[y].push_back(make_pair(x,z));
    }
    dfs(1,0);
    // for(int i=1;i<=n;i++) printf("%d ",in[i]);
    for(int i=1;i<=tot;i++) tr[i].gs=0;
    for(int i=1;i<=n;i++)
    {
        int val=query(rt[0],a[i]);
        insert(rt[0],a[i]); out[i]=-1;
        if((A^B)<val)
        {
            A=a[i];
            B=a[i]^val;
            p=i;
        }
    }
    for(int i=1;i<=n;i++) if(B==a[i]) q=i;
    calc(p); calc(q);
    for(int i=1;i<=n;i++) if(out[i]==-1) out[i]=A^B;
    int ans=0;
    for(int i=2;i<=n;i++) ans=max(ans,in[i]+out[i]);
    printf("%d\n",ans);
    return 0;
}
### Trie树数据结构的实现与应用 #### 什么是Trie树? Trie树,又称前缀树或字典树,是一种专门用于处理字符串集合的树形数据结构。它通过将公共前缀存储在同一路径上,从而有效地减少内存消耗并提高查询效率[^1]。 #### 基本特性 - **节点表示字符**:每个节点代表一个字符,根节点通常为空。 - **边连接子节点**:每条边指向下一个字符对应的子节点。 - **叶子节点标志结束**:某些实现中,叶子节点可能标记某个完整的字符串的结尾。 这种设计使得Trie树非常适合执行以下操作: - 插入新字符串。 - 查找特定字符串是否存在。 - 自动补全给定前缀的所有匹配项。 - 统计具有相同前缀的字符串数量。 --- #### C#中的Trie树实现 以下是基于C#的一个简单Trie树实现: ```csharp public class TrieNode { public Dictionary<char, TrieNode> Children { get; set; } public bool IsEndOfWord; public TrieNode() { Children = new Dictionary<char, TrieNode>(); IsEndOfWord = false; } } public class Trie { private readonly TrieNode root; public Trie() { root = new TrieNode(); } // 插入方法 public void Insert(string word) { var node = root; foreach (var c in word) { if (!node.Children.ContainsKey(c)) node.Children[c] = new TrieNode(); node = node.Children[c]; } node.IsEndOfWord = true; } // 查询方法 public bool Search(string word) { var node = root; foreach (var c in word) { if (!node.Children.ContainsKey(c)) return false; node = node.Children[c]; } return node.IsEndOfWord; } // 判断是否有以prefix开头的单词 public bool StartsWith(string prefix) { var node = root; foreach (var c in prefix) { if (!node.Children.ContainsKey(c)) return false; node = node.Children[c]; } return true; } } ``` 上述代码定义了一个基本的Trie树类及其核心功能——插入、查找以及判断前缀的存在性。 --- #### Java中的Trie树实现 下面是一个简单的Java版本的Trie树实现: ```java class TrieNode { Map<Character, TrieNode> children; boolean isEndOfWord; public TrieNode() { this.children = new HashMap<>(); this.isEndOfWord = false; } } public class Trie { private final TrieNode root; public Trie() { root = new TrieNode(); } // 插入方法 public void insert(String word) { TrieNode current = root; for (char ch : word.toCharArray()) { current.children.putIfAbsent(ch, new TrieNode()); current = current.children.get(ch); } current.isEndOfWord = true; } // 查询方法 public boolean search(String word) { TrieNode current = root; for (char ch : word.toCharArray()) { if (!current.children.containsKey(ch)) return false; current = current.children.get(ch); } return current.isEndOfWord; } // 判断是否有以prefix开头的单词 public boolean startsWith(String prefix) { TrieNode current = root; for (char ch : prefix.toCharArray()) { if (!current.children.containsKey(ch)) return false; current = current.children.get(ch); } return true; } } ``` 此实现在逻辑上类似于C#版,但在语法细节上有差异[^2]。 --- #### 应用场景 Trie树因其高效的前缀匹配能力,在许多实际问题中有广泛应用: 1. **拼写检查器**:快速验证输入是否为合法单词。 2. **搜索引擎建议**:根据用户输入的部分关键词推荐完整选项。 3. **IP路由表管理**:利用最长前缀匹配算法加速网络流量分发。 4. **DNA序列分析**:在生物信息学领域中寻找基因片段模式。 这些案例充分体现了Trie树的优势在于其针对字符串操作的高度优化性能[^3]。 ---
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值