[NEFU ACM大一暑假集训 解题报告]字典树
Posted 鱼竿钓鱼干
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[NEFU ACM大一暑假集训 解题报告]字典树相关的知识,希望对你有一定的参考价值。
[NEFU ACM大一暑假集训 解题报告]字典树
题目
A - L 语言
多模式匹配,AC自动机建立Trie图。不过这个题数据量很小,貌似可以暴力建立跳转关系,加上标记处理即可。
对于样例的AC自动机(Trie图)应该长下面这样,省略了所有结点到根结点的跳转。
单纯Trie+暴力建立跳转关系+标记应该也可以实现。AC自动机在Trie树上使用KMP的思想进行多模式匹配。主要的话,优化了跳转关系的建立(fail指针)。
fail指针或者说失配指针其实就是一种反悔的操作。我们走某一条路径,走到一般发现走不下去了,就想要回去走其他路径,而失配指针直接建立fail边,让你跳转到要去的地方(上图红色的边),大致明白这个思想就行。
其实感觉如果AC自动机做难点主要在怎么写匹配过程(因为其他都是板子哈哈哈)
匹配求答案过程借鉴洛谷这篇题解
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<map>
#include<utility>
#include<set>
#include<vector>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int,int>PII;
#define endl '\\n'
//CHECK MULTIPLY INPUT !!!
//NEW DATA CLEAN !!!
//THINK > CODE !!!
const int N=2E6+10;
int n,m;
int tr[N][26],cnt[N],idx;
char str[N];
int q[N],fail[N];
bool vis[N];
void insert(){
int p=0;
for(int i=0;str[i];i++){
int t=str[i]-'a';
if(!tr[p][t])tr[p][t]=++idx;
p=tr[p][t];
}
cnt[p]=strlen(str);
}
void build(){
int hh=0,tt=-1;
for(int i=0;i<26;i++){//从第一层开始搜索
if(tr[0][i])q[++tt]=tr[0][i];
}
while(hh<=tt){//BFS构建Trie图
int t=q[hh++];
for(int i=0;i<26;i++){
int p=tr[t][i];
if(!p)tr[t][i]=tr[fail[t]][i];
else{
fail[p]=tr[fail[t]][i];
q[++tt]=p;
}
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++){//构建Trie树
scanf(" %s",str);
insert();
}
build();//AC自动机建Trie图
while(m--){//AC自动机匹配
int res=0;
scanf("%s",str);
memset(vis,0,sizeof vis);
vis[0]=1;
for(int i=0,j=0;str[i];i++){
int t=str[i]-'a';
j=tr[j][t];//Trie图的某个结点
int p=j;
while(p){
if (vis[i - cnt[p] + 1]){//判断之前是否存在一个合法前缀
vis[i + 1] = true;//标记一下
break;//一个优化,如果找到了就直接退出,不然会T掉最后一个点
}
p=fail[p];//向上回溯,防止漏答案
}
if(vis[i+1])res=i+1;
}
printf("%d\\n",res);
}
return 0;
}
B - Secret Message 秘密信息
插入的时候记录每个结点经过的次数以及以该结点为结尾的数量。相减去掉重复部分即可。
#include<bits/stdc++.h>
using namespace std;
const int N=10010,M=500010;
int a[N],son[M][2],ed[M],pas[M],idx;
void insert(int* a,int k){
int p=0;
for(int i=0;i<k;i++){
if(!son[p][a[i]])son[p][a[i]]=++idx;
p=son[p][a[i]];
pas[p]++;
}
ed[p]++;
}
int query(int* a,int k){
int res=0,p=0;
for(int i=0;i<k;i++){
if(!son[p][a[i]])return res;
p=son[p][a[i]];
res+=ed[p];
}
return res+pas[p]-ed[p];
}
int main(){
int n,m;cin>>n>>m;
for(int i=0;i<n;i++){
int k;cin>>k;
for(int j=0;j<k;j++)cin>>a[j];
insert(a,k);
}
for(int i=0;i<m;i++){
int k;cin>>k;
for(int j=0;j<k;j++)cin>>a[j];
cout<<query(a,k)<<endl;
}
return 0;
}
C - The XOR-longest Path
先做D题。
根据异或前缀和性质:
l
,
r
区
间
异
或
和
为
s
u
m
[
r
]
x
o
r
s
u
m
[
l
−
1
]
l,r区间异或和为sum[r] xor sum[l-1]
l,r区间异或和为sum[r]xorsum[l−1]
那么,在trie树上的路径异或和等于两个叶子结点到根节点的异或和。
我们先用vector存图,再用dfs求解叶结点到根结点的异或和。这样问题就变成D题,求解最大异或对了。
#include<bits/stdc++.h>
using namespace std;
const int N=1000010,M=N*31;
typedef long long LL;
typedef pair<LL,LL> PIL;
vector<PIL>root[N];
LL idx;
LL a[N],son[M][2];
void insert(LL x){
LL p=0;
for(LL i=31;i>=0;i--){
LL &s=son[p][x>>i&1];
if(!s)s=++idx;
p=s;
}
}
LL query(LL x){
LL p=0,res=0;
for(LL i=31;i>=0;i--){
LL u=x>>i&1;
if(son[p][!u]){
res=res*2+1;
p=son[p][!u];
}
else{
res=res*2;
p=son[p][u];
}
}
return res;
}
void dfs(LL u,LL fa,LL sum){
a[u]=sum;
for(auto j:root[u]){
if(j.first==fa)continue;
dfs(j.first,u,j.second^sum);
}
}
int main(){
LL n;cin>>n;
for(LL i=1;i<n;i++){
LL u,v;LL w;cin>>u>>v>>w;
root[u].push_back({v,w});
root[v].push_back({u,w});
}
dfs(1,-1,0);
for(LL i=1;i<=n;i++)insert(a[i]);
LL ans=0;
for(LL i=1;i<=n;i++)ans=max(ans,query(a[i]));
cout<<ans;
return 0;
}
D - The XOR Largest Pair
异或性质:相同为0,相异为1。
query查询过程优先走相反的即可。
Trie树每往下走一层,就相当于答案向左移动一次,如果存在相反的那么还要进一。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10,M=31*N;
int a[N],son[M][2],idx;
void insert(int x){
int p=0;
for(int i=30;~i;i--){
int &s=son[p][x>>i&1];
if(!s)s=++idx;//创建新节点
p=s;
}
}
int query(int x){
int res=0,p=0;
for(int i=30;~i;i--){
int s=x>>i&1;
if(son[p][!s]){//看相反的是否存在
res=res*2+1;//答案左移进一
p=son[p][!s];
}
else{
p=son[p][s];
res=res*2;
}
}
return res;
}
int main(void)
{
cin.tie(0);
int n;
cin>>n;
idx=0;
for(int i=0;i<n;i++)
{
cin>>a[i];
insert(a[i]);
}
int res=0;
for(int i=0;i<n;i++)
{
res=max(res,query(a[i]));
}
cout<<res<<endl;
}
E - Phone List
注意多组重置trie树啊啊啊
貌似只要用pas数组记录结点经过次数,然后查询的时候看是不是每个结点出现次数>=2次了,如果存在只出现一次的就不行。
#include<bits/stdc++.h>
using namespace std;
const int N=1E4+10,M=1e5+10;
int son[M][12],pas[M],idx;
char str[M][12];
bool flag;
void insert(char* str){
int p=0;
for(int i=0;str[i];i++){
int ch=str[i]-'0';
if(!son[p][ch])son[p][ch]=++idx;
p=son[p][ch];
pas[p]++;
}
}
bool query(char* str){
int p=0;
for(int i=0;str[i];i++){
int u=str[i]-'0';
if(pas[son[p][u]]==1)return false;
p=son[p][u];
}
return true;
}
int main(){
int T;cin>>T;
while(T--){
int n;cin>>n;
flag=true;
idx=0;
memset(pas,0,sizeof pas);
memset(son,0,sizeof son);
for(int i=0;i<n;i++){
cin>>str[i];
insert(str[i]);
}
for(int i=0;i<n;i++){
if(query(str[i])){
flag=false;
break;
}
}
if(flag)puts("YES");
else 以上是关于[NEFU ACM大一暑假集训 解题报告]字典树的主要内容,如果未能解决你的问题,请参考以下文章