BZOJ3926 [Zjoi2015]诸神眷顾的幻想乡 广义后缀自动机

神々が恋した幻想郷 ~ Kamigami ga Koishita Gensoukyou

题意:一棵树点上有字符,求树上路径所组成的本质不同的字符串的个数,保证叶子数<=20

Sol:

陈老师出的题..Orz

首先由一个结论:树上任意一条简单路径都可以被以某个叶子为根的树上的一条纵向链表示,即枚举每个叶子作为根,所有纵向链的并集=所有路径的集合

简单证明:随便选一个叶子作根,不是纵向链的简单路径一定能被其他两个叶子作根时表示。

那我们可以枚举每个叶子为根DFS,建出一棵Trie树,求Trie树上本质不同的字符串个数

用广义后缀自动机实现,具体可以参考2015年国家集训队论文或者陈老师的博客

事实上不用存储Trie树的形态,DFS过程中用一个变量维护位置就行

写完这题打通了风神录Hard(虽然还是炸过去的QAQ  

Code:

#include<bits/stdc++.h>
#define PAD(x,y) memset(x,y,sizeof x)
#define CPY(x,y) memcpy(x,y,sizeof x)
using namespace std;
const int N = 100009, M = N * 20;
const int alpha = 10;

int n,m;
struct SAM
{
	int tot,root;
	struct state
	{
		int son[alpha];
		int mx,par;
	}node[M<<1];
	void init()
	{
		tot=root=1;
		PAD(node,0);
	}
	int extend(int p,int x)
	{
		if(node[p].son[x])
		{
			int q=node[p].son[x];
			if(node[p].mx+1==node[q].mx) return q;
			else
			{
				int nq=++tot;
				CPY(node[nq].son,node[q].son);
				node[nq].mx=node[p].mx+1;
				node[nq].par=node[q].par;
				node[q].par=nq;
				for(;p&&node[p].son[x]==q;p=node[p].par) node[p].son[x]=nq;
				return nq;
			}
		}
		else
		{
			int np=++tot;
			node[np].mx=node[p].mx+1;
			for(;p&&node[p].son[x]==0;p=node[p].par) node[p].son[x]=np;
			if(p==0) node[np].par=root;
			else
			{
				int q=node[p].son[x];
				if(node[p].mx+1==node[q].mx) node[np].par=q;
				else
				{
					int nq=++tot;
					CPY(node[nq].son,node[q].son);
					node[nq].mx=node[p].mx+1;
					node[nq].par=node[q].par;
					node[np].par=node[q].par=nq;
					for(;p&&node[p].son[x]==q;p=node[p].par) node[p].son[x]=nq;
				}
			}
			return np;
		}
	}
	void solve()
	{
		long long ans=0;
		for(int i=1;i<=tot;i++) ans+=(node[i].mx-node[node[i].par].mx);
		printf("%lld\n",ans);
	}
}uuz;
int first[N];
struct edg
{
	int next;
	int to;
}e[N<<1];
int e_sum;
int col[N],degree[N];

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
inline void add_edg(int x,int y)
{
	e_sum++;
	e[e_sum].next=first[x];
	first[x]=e_sum;
	e[e_sum].to=y;
}

void dfs(int x,int fa,int last)
{
	int nxt=uuz.extend(last,col[x]);
	for(int i=first[x];i;i=e[i].next)
	{
		int w=e[i].to;
		if(w==fa) continue;
		dfs(w,x,nxt);
	}
}

int main()
{
	n=read();m=read();
	for(int i=1;i<=n;i++) col[i]=read();
	for(int i=1;i<n;i++)
	{
		int x=read(),y=read();
		degree[x]++;degree[y]++;
		add_edg(x,y);add_edg(y,x);
	}
	uuz.init();
	for(int i=1;i<=n;i++) if(degree[i]==1) dfs(i,0,uuz.root);
	uuz.solve();
	return 0;
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值