1 条题解
-
0
思路
每次询问 的简单路径上有多少个字符串以 为前缀,不难想到用 trie 树去维护。而普通的 trie 只能查询所有字符串中产生的答案,对于这类区间询问,就要用到可持久化 trie 树了。不会右转可持久化 trie 树模板题。
的简单路径上编号不连续,非要把它拉到序列上可以树剖,麻烦了。树上两点间的查询还有一个简单思路:求出他们的 lca 为 ,那么答案就是 。其中 表示 到根节点的路径上,有多少个字符串以 为前缀。这样,我们就只需要解决 到根节点的问题了。
首先把边上的字符串转化到点上,因为每个点只有一个父亲,因此 这条路径上的字符串就可以记为 。然后按照 dfs 序建立每个点 trie 树的 (可持久化),每次从这个点的父亲的 继承过来,那么下次访问这个点时就只会统计他到根节点上的答案了。算答案可以记一个 表示 trie 树上经过了 这个点的字符串有多少个。与普通 trie 不同的是,他要记录当前点到根节点的所有答案,因此在插入时 不能单纯地 ,而是用前缀和的形式要继承它父亲的答案,即 。查询如果发现一个点 值为 ,那就说明没有 这个字符串,
return 0就行。否则一直走完 ,最后return sum[p]。code
#include<bits/stdc++.h> #define pb push_back #define ll long long #define mkp make_pair using namespace std; typedef pair<int,string>pii; const int N=1e5+5; int n,m,k,cnt,tot,dep[N]; int f[25][N],root[N]; vector<pii>e[N]; int tr[N*10][28],sum[N*10]; int work(char c){ return c-'a'+1; } void insert(int pre,int p,string s){ for(int i=0;i<s.size();++i){ int u=work(s[i]); if(pre)for(int j=1;j<=26;++j)if(j!=u)tr[p][j]=tr[pre][j];//继承 tr[p][u]=++cnt; p=tr[p][u]; pre=tr[pre][u]; sum[p]=sum[pre]+1;//更新前缀和 } } int query(int p,string s){ for(int i=0;i<s.size();++i){ int u=work(s[i]); if(!tr[p][u])return 0;//没找到返回0 p=tr[p][u]; } return sum[p]; } void dfs(int u,int fa){ f[0][u]=fa; dep[u]=dep[fa]+1; for(int i=1;i<=20;++i)f[i][u]=f[i-1][f[i-1][u]]; for(auto tmp:e[u]){ int v=tmp.first; string s=tmp.second; if(v==fa)continue; root[v]=++cnt; insert(root[u],root[v],s);//每次继承它父亲的trie dfs(v,u); } } int lca(int x,int y){ if(dep[x]<dep[y])swap(x,y); for(int i=20;i>=0;--i)if(dep[f[i][x]]>=dep[y])x=f[i][x]; if(x==y)return x; for(int i=20;i>=0;--i) if(f[i][x]!=f[i][y]){ x=f[i][x]; y=f[i][y]; } return f[0][x]; } int main(){ cin>>n; for(int i=1;i<n;++i){ int a,b; string s; cin>>a>>b>>s; e[a].pb(mkp(b,s));e[b].pb(mkp(a,s)); } dfs(1,0); int q; cin>>q; while(q--){ int u,v;string s; cin>>u>>v>>s; cout<<query(root[u],s)+query(root[v],s)-2*query(root[lca(u,v)],s)<<endl; } return 0; }
- 1
信息
- ID
- 6142
- 时间
- 2000ms
- 内存
- 512MiB
- 难度
- 10
- 标签
- 递交数
- 3
- 已通过
- 3
- 上传者