KMP、trie、hash练习总结

P5043 【模板】树同构

1.有根树最小表示:

树的括号序列转化:从树根开始执行一次 DFS,每一个结点的子树都有进入和回溯两个过程。DFS 过程中维护一个序列,进入一个子树时向序列中加入一个左括号,回溯时向序列中加入一个右括号,如此可以构成长度为 2×n 的括号序列。

然而 DFS 过程中,一个点 u 有多个子结点 v1,v2,⋯ ,vk,因此在不规定访问顺序的情况下,同一棵树有多种不同的括号序列,其中字典序最小的一个称为这颗有根树的最小表示

最小表示法的性质:两颗有根树树同构的充要条件是其最小表示相同。

求法:DFS 一次即可,设 fi 表示以 i 为根的子树的最小表示。设一个点 u 的子结点分别为 v1,⋯ ,vk,则将 fv1​​,⋯,fvk​​ 按字典序从小到大排序,有:fu=(fv1⋯fvn),字符之间直接连接。

2.无根树的同构:

一种简单粗暴的方法:对于树 T,枚举每个点 i 为根求出最小表示 ansi​,令 ans(T)=min⁡(ansi),得到所有最小表示中的字典树最小值。这样,两棵树 T1,T2 同构的充要条件就是 ans(T1)=ans(T2)。

这个方法的原理其实是无根树转成有根树,令 ansiansi​ 取到最小值的 i 为 minroot(T),则 ans(T1)=ans(T2) 即 ansminroot(T1)=ansminroot(T2),而无根树要同构,只需要以某个点为根转化为有根树后同构即可。

代码呈上:

#include <bits/stdc++.h>
using namespace std;
const int MAXN=60;
int t,n,x,eg,mnp,flg,hd[MAXN],ver[2*MAXN],nx[2*MAXN],sz[MAXN],mx[MAXN];
string mn[MAXN],f[MAXN],g[MAXN],tmp;
void add_edge (int x,int y) {
	ver[++eg]=y;
	nx[eg]=hd[x];
	hd[x]=eg;
	return;
}
void dfs1 (int x,int fa) {
	sz[x]=1;
	for (int i=hd[x];i;i=nx[i]) {
		if (ver[i]==fa) {continue;}
		dfs1(ver[i],x);
		sz[x]+=sz[ver[i]];
		mx[x]=max(mx[x],sz[ver[i]]);
	}
	mx[x]=max(mx[x],n-sz[x]);
	mnp=min(mnp,mx[x]);
	return;
}
void dfs2 (int x,int fa) {
	f[x]="0";
	for (int i=hd[x];i;i=nx[i]) {
		if (ver[i]==fa) {continue;}
		dfs2(ver[i],x);
	}
	int tot=0;
	for (int i=hd[x];i;i=nx[i]) {
		if (ver[i]==fa) {continue;}
		g[++tot]=f[ver[i]];
	}
	sort(g+1,g+tot+1);
	for (int i=1;i<=tot;i++) {f[x]+=g[i];}
	f[x]+="1";
	return;
}
int main () {
	scanf("%d",&t);
	for (int ii=1;ii<=t;ii++) {
		scanf("%d",&n);
		memset(hd,0,sizeof(hd));
		memset(mx,0,sizeof(mx));
		eg=flg=0,mnp=n+1;
		tmp="1";
		for (int i=1;i<=n;i++) {
			scanf("%d",&x);
			if (x) {add_edge(x,i),add_edge(i,x);}
		}
		dfs1(1,0);
		for (int i=1;i<=n;i++) {
			if (mx[i]==mnp) {
				dfs2(i,0);
				tmp=min(tmp,f[i]);
			}
		}
		mn[ii]=tmp;
		for (int i=1;i<=ii-1;i++) {
			if (mn[i]==mn[ii]) {
				printf("%d\n",i),flg=1;
				break;
			}
		}
		if (!flg) {printf("%d\n",ii);}
	}
	return 0;
}

P3065 [USACO12DEC] First! G

1.涉及到字典序的问题,那么应该能想到字典树。很显然字符串s1如果比字符串s2的字典序小的话,只有两种情况,s1是s2的前缀;以及他们有相同的前缀单在相同前缀后面的部分那一部分s1的优先级更大;所以将所以字符串建成一棵字典树,然后再在字典树上遍历每一串字符串,如果有字符串已经是他的前缀,无论怎么改变26个字母的排列也无济于事,否则判断优先级,及上文所说的情况二。

2.字符串s1 为 mma 字符串s2 为 mmb,现在要使mmb的优先级大于mma的优先级,那如何判断通过26个字母的变换后mmb能否优先于mma呢,应该一眼看出把a,b交换位置后s2的字典序就小于s1了,那么接下来就是要找到一种证明的方法;他们都有相同的mm前缀,那么要使s2的优先级大,就要使b的优先级大,及相同前缀后面部分的优先级大,跟之前所说的一样。

设计到优先级的问题我们绞尽脑汁后应该能想到一个叫拓扑序的神奇的东西,构造这样的图,此时a,b在拓扑排序后的顺序是在同一层的,所以可以瞎把a,b交换位置都可以,故可以将a,b交换位置使得s2的优先级大于s1;

再来一个反例加深理解:无法构成拓扑序,因为a,b无论怎么交换位置,mmab都在mmba前面

#include<bits/stdc++.h>
using namespace std;
struct Node{
	int son[27],end;
}N[300010];
vector<int> E[27];
int n,cnt=1,ind[30010],ans_sum,used[27][27];
string ans[30010],s[30010];
void insert(string s){
	int now=1;
	for(int i=0;i<s.length();i++){
		if(!N[now].son[s[i]-'a']) N[now].son[s[i]-'a']=++cnt;
		now=N[now].son[s[i]-'a'];
	}
	N[now].end++;
}
int work(string s){
	int now=1;
	for(int i=0;i<s.length();i++){
		int t=s[i]-'a';
		if(N[now].end) return 0;
		for(int j=0;j<26;j++){
			if(N[now].son[j]&&t!=j){
				E[t].push_back(j);
				ind[j]++;
			}
		}
		now=N[now].son[t];
	}
	return 1;
}
int check(){
	queue<int> q;
	for(int i=0;i<26;i++) if(!ind[i]) q.push(i);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=0;i<E[u].size();i++){
			ind[E[u][i]]--;
			if(!ind[E[u][i]]) q.push(E[u][i]);
		}
	}
	for(int i=0;i<26;i++) if(ind[i]) return 0;
	return 1;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		cin>>s[i];
		insert(s[i]);
	}
	for(int i=1;i<=n;i++){
		memset(used,0,sizeof(used));
		memset(ind,0,sizeof(ind));
		for(int j=0;j<27;j++) E[j].clear();
		if(!work(s[i])) continue;
		if(check()) ans[++ans_sum]=s[i];
 	}
 	printf("%d\n",ans_sum);
 	for(int i=1;i<=ans_sum;i++) cout<<ans[i]<<endl;
	return 0;
}

P4407 [JSOI2009] 电子字典

1.简单理解下题目:有 m 次查询,每次查询给定一个字符串 Qi​,求在已给出的 n 个字符串 Gi​ 中,有多少个字符串与 Qi​ 的编辑距离为 1;若 Qi​ 在 G 中出现过,则返回 −1。

2.

注意到串长都是极短的,所以我们考虑 Trie。

对于每次查询,我们都在直接 Trie 上做 DFS。考虑暴力枚举出对于某一位添加一个字符、替换一个字符、删除一个字符后的串是否在 Trie 上。

        删除:即直接跳过当前字符。

        替换:暴力枚举替换当前位的字母,继续在往下走即可。

        添加:与替换基本相同。

最后代码呈上:

#include <bits/stdc++.h>
using namespace std;
const int N=1e4+5;
const int W=25;
int n,m,tot,ans,len;
char s[W];
int trie[N*20][30],c[N];
bool is[N*20],vis[N*20],mark;
void Insert()
{
	int p=0;
	for (int i=0;i<len;i++)
	{
		int ch=s[i]-'a';
		if (trie[p][ch]==0) trie[p][ch]=++tot;
		p=trie[p][ch];
	}
	is[p]=1;
}
void dfs(int u,int L,bool flg)
{
	if (L==len&&is[u]&&(!flg))
	{
		mark=1;
		return ;
	}
	if (L==len&&is[u]&&flg)
	{
		if (!vis[u]) vis[c[++ans]=u]=1;
		return ;
	}
	int ch=s[L]-'a';
	if (!flg)
	{
		if (L<len) dfs(u,L+1,1);
		for (int i=0;i<26;i++)
		{
			if (trie[u][i]==0) continue;
			dfs(trie[u][i],L,1);
			if (i!=ch) dfs(trie[u][i],L+1,1);
		}
	}
	if (L>=len) return ;
	if (trie[u][ch]!=0) dfs(trie[u][ch],L+1,flg);
}
int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
	{
		scanf("%s",s);
		len=strlen(s);
		Insert();
	}
	for (int i=1;i<=m;i++)
	{
		scanf("%s",s);
		len=strlen(s);
		dfs(0,0,0);
		if (mark) printf("-1\n");
		else printf("%d\n",ans);
		while (ans) vis[c[ans--]]=0;
		mark=0;
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值