题链:

http://www.lydsy.com/JudgeOnline/problem.php?id=3926
题解&&代码:

后缀自动机,Trie树

如果以每个叶子为根,所有的子串一定在某一颗树的一条由祖先到子孙的链上。
由于叶子节点只有不超过20个,那么就可以从每个叶子开始dfs,把每个从根开始的串都加入一颗trie树。
显然,所有的子串都在trie树上,那么现在就需要统计trie树上有多少不同的子串。
对trie树建立后缀自动机,然后统计不同的子串个数即可。
(本人不会在线建立trie树的后缀自动机,所以就写了一个离线BFS trie树建后缀自动机)

#include<bits/stdc++.h>
#define MAXN 100005
#define ll long long
using namespace std;
ll cnt[MAXN*20];
int color[MAXN],N,C;
struct Edge{
int ent;
int to[MAXN*2],nxt[MAXN*2],head[MAXN];
Edge(){ent=2;}
void Adde(int u,int v){
to[ent]=v; nxt[ent]=head[u]; head[u]=ent++;
to[ent]=u; nxt[ent]=head[v]; head[v]=ent++;
}
}E;
struct Trie{
int size;
int ch[MAXN*20][10];
int Trans(int last,int x){
if(ch[last][x]) return ch[last][x];
return ch[last][x]=++size;
}
void Reset(){size=1;}
}T;
struct SAM{
int size;
int maxs[MAXN*20],trans[MAXN*20][10],parent[MAXN*20];
int Newnode(int a,int b){
++size; maxs[size]=a;
memcpy(trans[size],trans[b],sizeof(trans[b]));
return size;
}
int Extend(int last,int x){
static int p,np,q,nq;
p=last; np=Newnode(maxs[p]+1,0);
for(;p&&!trans[p][x];p=parent[p]) trans[p][x]=np;
if(!p) parent[np]=1;
else{
q=trans[p][x];
if(maxs[p]+1!=maxs[q]){
nq=Newnode(maxs[p]+1,q);
parent[nq]=parent[q];
parent[q]=parent[np]=nq;
for(;p&&trans[p][x]==q;p=parent[p]) trans[p][x]=nq;
}
else parent[np]=q;
}
return np;
}
void Reset(){
memset(trans[0],0,sizeof(trans[0]));
size=0; Newnode(0,0);
}
void Count(){
static queue<int>Q;
static int order[MAXN*20],in[MAXN*20],ont;
for(int p=1;p<=size;p++)
for(int c=0;c<10;c++) if(trans[p][c])
in[trans[p][c]]++;
Q.push(1);
while(!Q.empty()){
int p=Q.front(); Q.pop(); order[++ont]=p;
for(int c=0;c<10;c++) if(trans[p][c]){
in[trans[p][c]]--;
if(!in[trans[p][c]]) Q.push(trans[p][c]);
}
}
for(int i=size,p;i;i--){
p=order[i]; cnt[p]=(p==1?0:1);
for(int c=0;c<10;c++) if(trans[p][c])
cnt[p]+=cnt[trans[p][c]];
}
}
}SUF;
void dfs(int u,int dad,int p){
p=T.Trans(p,color[u]);
for(int i=E.head[u];i;i=E.nxt[i])
if(E.to[i]!=dad) dfs(E.to[i],u,p);
}
void bfs(){
static int state[MAXN*20];
static queue<int>Q;
Q.push(1); state[1]=1;
while(!Q.empty()){
int u=Q.front(); Q.pop();
for(int c=0;c<10;c++) if(T.ch[u][c]){
state[T.ch[u][c]]=SUF.Extend(state[u],c);
Q.push(T.ch[u][c]);
}
}
}
int main(){
//freopen("substring.in","r",stdin);
//freopen("substring.out","w",stdout);
static int in[MAXN];
scanf("%d%d",&N,&C);
SUF.Reset(); T.Reset();
for(int i=1;i<=N;i++) scanf("%d",&color[i]);
for(int i=1,a,b;i<N;i++)
scanf("%d%d",&a,&b),E.Adde(a,b),in[a]++,in[b]++;
for(int i=1;i<=N;i++) if(in[i]==1) dfs(i,0,1);
bfs();
SUF.Count();
printf("%lld\n",cnt[1]);
return 0;
}

  

然后看了别人的做法,发现利用后缀自动机里面每个状态的不重复的性质性,还可以有更简便的求不同子串个数的方法(SUF.Count()有变化,效率提升了些)。

#include<bits/stdc++.h>
#define MAXN 100005
#define ll long long
using namespace std;
ll cnt;
int color[MAXN],N,C;
struct Edge{
int ent;
int to[MAXN*2],nxt[MAXN*2],head[MAXN];
Edge(){ent=2;}
void Adde(int u,int v){
to[ent]=v; nxt[ent]=head[u]; head[u]=ent++;
to[ent]=u; nxt[ent]=head[v]; head[v]=ent++;
}
}E;
struct Trie{
int size;
int ch[MAXN*20][10];
int Trans(int last,int x){
if(ch[last][x]) return ch[last][x];
return ch[last][x]=++size;
}
void Reset(){size=1;}
}T;
struct SAM{
int size;
int maxs[MAXN*20],trans[MAXN*20][10],parent[MAXN*20];
int Newnode(int a,int b){
++size; maxs[size]=a;
memcpy(trans[size],trans[b],sizeof(trans[b]));
return size;
}
int Extend(int last,int x){
static int p,np,q,nq;
p=last; np=Newnode(maxs[p]+1,0);
for(;p&&!trans[p][x];p=parent[p]) trans[p][x]=np;
if(!p) parent[np]=1;
else{
q=trans[p][x];
if(maxs[p]+1!=maxs[q]){
nq=Newnode(maxs[p]+1,q);
parent[nq]=parent[q];
parent[q]=parent[np]=nq;
for(;p&&trans[p][x]==q;p=parent[p]) trans[p][x]=nq;
}
else parent[np]=q;
}
return np;
}
void Reset(){
memset(trans[0],0,sizeof(trans[0]));
size=0; Newnode(0,0);
}
void Count(){
for(int p=1;p<=size;p++) cnt+=maxs[p]-maxs[parent[p]];
}
}SUF;
void dfs(int u,int dad,int p){
p=T.Trans(p,color[u]);
for(int i=E.head[u];i;i=E.nxt[i])
if(E.to[i]!=dad) dfs(E.to[i],u,p);
}
void bfs(){
static int state[MAXN*20];
static queue<int>Q;
Q.push(1); state[1]=1;
while(!Q.empty()){
int u=Q.front(); Q.pop();
for(int c=0;c<10;c++) if(T.ch[u][c]){
state[T.ch[u][c]]=SUF.Extend(state[u],c);
Q.push(T.ch[u][c]);
}
}
}
int main(){
// freopen("substring.in","r",stdin);
// freopen("substring.out","w",stdout);
static int in[MAXN];
scanf("%d%d",&N,&C);
SUF.Reset(); T.Reset();
for(int i=1;i<=N;i++) scanf("%d",&color[i]);
for(int i=1,a,b;i<N;i++)
scanf("%d%d",&a,&b),E.Adde(a,b),in[a]++,in[b]++;
for(int i=1;i<=N;i++) if(in[i]==1) dfs(i,0,1);
bfs();
SUF.Count();
printf("%lld\n",cnt);
return 0;
}

  

然后想去学学在线对trie树建立后缀自动机,但是论文看得我脑袋疼。。。
这时突然发现其他博主的代码也是可以在线增量的,似乎叫广义后缀自动机。。。,
感觉看代码的实现似乎没毛病,而且还避免了建trie树。只是出现了一些无法到达的状态。

#include<bits/stdc++.h>
#define MAXN 100005
#define ll long long
using namespace std;
int N,C;
int color[MAXN];
struct Edge{
int to[MAXN*2],nxt[MAXN*2],head[MAXN],ent;
Edge(){ent=2;}
void Adde(int u,int v){
to[ent]=v; nxt[ent]=head[u]; head[u]=ent++;
to[ent]=u; nxt[ent]=head[v]; head[v]=ent++;
}
}E;
struct SAM{
int size;
int maxs[MAXN*20],trans[MAXN*20][26],parent[MAXN*20];
int Newnode(int a,int b){
++size; maxs[size]=a;
memcpy(trans[size],trans[b],sizeof(trans[b]));
return size;
}
int Extend(int last,int x){
static int p,np,q,nq; p=last;
if(trans[p][x]&&maxs[p]+1==maxs[trans[p][x]]) return trans[p][x];
np=Newnode(maxs[p]+1,0);
for(;p&&!trans[p][x];p=parent[p]) trans[p][x]=np;
if(!p) parent[np]=1;
else{
q=trans[p][x];
if(maxs[p]+1!=maxs[q]){
nq=Newnode(maxs[p]+1,q);
parent[nq]=parent[q];
parent[q]=parent[np]=nq;
for(;p&&trans[p][x]==q;p=parent[p]) trans[p][x]=nq;
}
else parent[np]=q;
}
return np;
}
void Reset(){
memset(trans[0],0,sizeof(trans[0]));
size=0; Newnode(0,0);
}
ll Count(ll cnt=0){
for(int p=1;p<=size;p++) cnt+=maxs[p]-maxs[parent[p]];
return cnt;
}
}SUF;
void dfs(int u,int dad,int last){
last=SUF.Extend(last,color[u]);
for(int i=E.head[u];i;i=E.nxt[i])
if(E.to[i]!=dad) dfs(E.to[i],u,last);
}
int main(){
freopen("substring.in","r",stdin);
//freopen("substring.out","w",stdout);
static int in[MAXN];
SUF.Reset();
scanf("%d%d",&N,&C);
for(int i=1;i<=N;i++) scanf("%d",&color[i]);
for(int i=1,u,v;i<N;i++)
scanf("%d%d",&u,&v),E.Adde(u,v),in[u]++,in[v]++;
for(int i=1;i<=N;i++) if(in[i]==1) dfs(i,0,1);
printf("%lld\n%d\n",SUF.Count(),SUF.size);
return 0;
}

  

结果是比之前建了trie树的离线自动机构法多了一些状态,
(第20组数据做的测试,相比于建立Trie树后再离线bfs建后缀自动机多了2w个状态,估计就是那些无法到达的状态产生的)

然后我想:“如果还是先建立一颗trie树,再用上面的增量法去在线构造Trie树的后缀自动机,会不会减少一些无法到达的状态?”
试了一下,结果更慢了。。。(第20组数据做的测试,相比与不建Trie树直接在线增量法构造后缀自动机,多了1ow个状态。。。)
就不放代码了。

●BZOJ 3926 [Zjoi2015]诸神眷顾的幻想乡的更多相关文章

  1. BZOJ 3926: [Zjoi2015]诸神眷顾的幻想乡

    3926: [Zjoi2015]诸神眷顾的幻想乡 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 1017  Solved: 599[Submit][S ...

  2. 字符串(广义后缀自动机):BZOJ 3926 [Zjoi2015]诸神眷顾的幻想乡

    3926: [Zjoi2015]诸神眷顾的幻想乡 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 843  Solved: 510[Submit][St ...

  3. BZOJ 3926: [Zjoi2015]诸神眷顾的幻想乡 [广义后缀自动机 Trie]

    3926: [Zjoi2015]诸神眷顾的幻想乡 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 1124  Solved: 660[Submit][S ...

  4. bzoj 3926 [Zjoi2015]诸神眷顾的幻想乡(SAM)

    [题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=3926   [题意]   给定一棵树,每个节点都有相应的颜色,且保证叶子数不超过20,问 ...

  5. BZOJ 3926: [Zjoi2015]诸神眷顾的幻想乡 广义后缀自动机 后缀自动机 字符串

    https://www.lydsy.com/JudgeOnline/problem.php?id=3926 广义后缀自动机是一种可以处理好多字符串的一种数据结构(不像后缀自动机只有处理一到两种的时候比 ...

  6. 【刷题】BZOJ 3926 [Zjoi2015]诸神眷顾的幻想乡

    Description 幽香是全幻想乡里最受人欢迎的萌妹子,这天,是幽香的2600岁生日,无数幽香的粉丝到了幽香家门前的太阳花田上来为幽香庆祝生日. 粉丝们非常热情,自发组织表演了一系列节目给幽香看. ...

  7. BZOJ.3926.[ZJOI2015]诸神眷顾的幻想乡(广义后缀自动机)

    题目链接 要对多个串同时建立SAM,有两种方法: 1.将所有串拼起来,中间用分隔符隔开,插入字符正常插入即可. 2.在这些串的Trie上建SAM.实际上并不需要建Trie,还是只需要正常插入(因为本来 ...

  8. BZOJ 3926 [Zjoi2015]诸神眷顾的幻想乡 ——广义后缀自动机

    神奇的性质,叶子节点不超过20个. 然后把这些节点提出来构成一颗新树,那么这些树恰好包含了所有的情况. 所以直接广义后缀自动机. 然后统计本质不同的字符串就很简单显然了. #include <c ...

  9. bzoj 3926: [Zjoi2015]诸神眷顾的幻想乡【SAM】

    有一个显然的性质就是每个串一定在某个叶子为根的树中是一条直的链 然后因为SAM里是不会有相同状态的,所以以每个叶子为根dfs一遍,并且动态构造SAM(这里的节点u的last指向父亲),最后统计答案就是 ...

随机推荐

  1. 课后练习:C语言实现Linux命令——od

    课后练习:C语言实现Linux命令--od --------CONTENTS-------- 题目详情与分析 设计思路 遇到的问题及解决 待实现的设想与思考 学习反思与感悟 附1:myod.c「1.0 ...

  2. 2017-2018-1 20155201 《信息安全系统设计基础》 pwd命令的实现

    2017-2018-1 20155201 <信息安全系统设计基础> pwd命令的实现 一.对pwd命令的学习 在终端中输入man pwd查看手册中对pwd这一命令的解释: 以绝对路径的方式 ...

  3. Week1绪论--抽象数据类型

    一.作业题目 1.构造有理数T,元素e1,e2分别被赋以分子.分母值 2.销毁有理数T 3.用e(引用类型参数)返回有理数T的分子或分母,当入参i为1时返回分子, i为2是返回分母. 4.将有理数T的 ...

  4. 20162302 实验四《Android程序设计》实验报告

    实 验 报 告 课程:程序设计与数据结构 姓名:杨京典 班级:1623 学号:20162302 实验名称:Android程序设计 实验器材:装有Android Studio的联想拯救者80RQ 实验目 ...

  5. 团队作业4——第一次项目冲刺(Alpha版本) Day 2

    小队@JMUZJB-集美震惊部 一.Daily Scrum Meeting照片 二.Burndown Chart 燃尽图 三.项目进展 成员 工作 丘雨晨 环境配置 刘向东 数据库搭建,环境配置 江泽 ...

  6. Python打包分发工具setuptools

    作为Python标准的打包及分发工具,setuptools可以说相当地简单易用.它会随着Python一起安装在你的机器上.你只需写一个简短的setup.py安装文件,就可以将你的Python应用打包 ...

  7. Beta冲刺Day5

    项目进展 李明皇 今天解决的进度 服务器端还未完善,所以无法进行联动调试.对页面样式和逻辑进行优化 明天安排 前后端联动调试 林翔 今天解决的进度 完成维护登录态,实现图片上传,微信开发工具上传图片不 ...

  8. JAVA中最容易让人忽视的基础。

    可能很多找编程工作的人在面试的时候都有这种感受,去到一个公司填写面试试题的时候,多数人往往死在比较基础的知识点上.不要奇怪,事实就是如此一般来说,大多数公司给出的基础题大概有122道,代码题19道左右 ...

  9. monog和github学习

    1.导出服务器数据库到本地以json的格式储存:mongoexport -h ip -d dbname -c user -o D:\mondb\user.json2.导入本地Json到本地项目中:D: ...

  10. MongoDb进阶实践之五 MongoDB修改命令详述

    一.引言         上一篇文章我们已经详细介绍了MongoDB数据库的有关查询的内容,但是这只是所有查询命令的冰山一角.所有查询命令都写完也没有必要,我只是写了一些常用的命令,对MongoDB的 ...