并不对劲的字符串专题:Trie树

Posted xzyf

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并不对劲的字符串专题:Trie树相关的知识,希望对你有一定的参考价值。

据说这些并不对劲的内容是《信息学奥赛一本通提高篇》的配套练习。

并不会讲Trie树。

1.poj1056->技术分享图片

模板题。

2.bzoj1212->技术分享图片

设dp[i]表示T长度为i的前缀能否被理解。这样,对于所有满足T[(x+1)...i]是一个字典中的单词的x,dp[i]|=dp[x]。

所以,就可以将所有字典中的单词倒着建一棵Trie树,计算i时将T长度为i的前缀倒着在Trie树中匹配。

最后从后往前枚举i,第一个dp[i]=1的i就是答案。

#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iomanip>
#include<iostream>
#include<map>
#include<stack>
#include<set>
#include<queue>
#define maxn 2000010
#define maxnd 3010
using namespace std;
int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch)&&ch!=‘-‘)ch=getchar();
    if(ch==‘-‘)f=-1,ch=getchar();
    while(isdigit(ch))x=(x<<3)+(x<<1)+ch-‘0‘,ch=getchar();
    return x*f;
}
void write(int x)
{
    int f=0;char ch[20];
    if(x==0){putchar(‘0‘),putchar(‘
‘);return;}
    if(x<0){putchar(‘-‘),x=-x;}
    while(x)ch[++f]=x%10+‘0‘,x/=10;
    while(f)putchar(ch[f--]);
    putchar(‘
‘);
}
int ch[maxnd][26],cnt,go[maxnd],dp[maxn],n,m,ns,nt;
char t[maxnd],s[maxn];
int gx(char c){return c-‘a‘;}
void ext()
{
    int u=0;
    for(int i=nt;i>=1;i--)
    {
        if(!ch[u][gx(t[i])])ch[u][gx(t[i])]=++cnt;
        u=ch[u][gx(t[i])];
    }
    go[u]=1;
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<=n;i++)
    {
        scanf("%s",t+1);
        nt=strlen(t+1);
        ext();
    }
    //for(int i=0;i<=cnt;i++){for(int j=0;j<26;j++)if(ch[i][j])cout<<j<<":"<<ch[i][j]<<" ";cout<<endl;}
    for(int i=1;i<=m;i++)
    {
        scanf("%s",s+1);
        ns=strlen(s+1);dp[0]=1;
        for(int j=1;j<=ns;j++)
        {
            int u=0;dp[j]=0;//cout<<gx(s[j])<<"*"<<endl;
            for(int k=j;!dp[j]&&k>0&&ch[u][gx(s[k])];k--)
            {
                u=ch[u][gx(s[k])];
            //  cout<<go[u]<<endl;
                if(go[u])dp[j]|=dp[k-1];
            }
        }
        for(int j=ns;j>=0;j--)if(dp[j]){write(j);break;}
    }
    return 0;
}

3.bzoj1590->技术分享图片

听上去像裸题,但是很容易算晕。

先把所有密码前缀建成Trie树。

可以把FJ手里的每个串在所有密码前缀匹配的串分为两类:是它的前缀的串,它是这个串的前缀的串。

对于第一种,可以直接在Trie树中匹配,累加经过的点是几个串的结尾。

对于第二种,首先,如果在匹配过程中发现该走的边在Trie树中不存在,那么这一部分没有;如果匹配一直很顺利,最后走到了Trie树的一个节点x,那么答案就是子树x中一共有几个串的结尾。

#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iomanip>
#include<iostream>
#include<map>
#include<stack>
#include<set>
#include<queue>
#define maxn 500010
using namespace std;
int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch)&&ch!=‘-‘)ch=getchar();
    if(ch==‘-‘)f=-1,ch=getchar();
    while(isdigit(ch))x=(x<<3)+(x<<1)+ch-‘0‘,ch=getchar();
    return x*f;
}
void write(int x)
{
    int f=0;char ch[20];
    if(x==0){putchar(‘0‘),putchar(‘
‘);return;}
    if(x<0){putchar(‘-‘),x=-x;}
    while(x)ch[++f]=x%10+‘0‘,x/=10;
    while(f)putchar(ch[f--]);
    putchar(‘
‘);
}
int n,m,s[maxn],t[maxn],ch[maxn][2],val[maxn],w[maxn],ns,nt,cnt; 
void ext()
{
    int u=0;
    for(int i=1;i<=nt;i++)
    {
        if(!ch[u][t[i]])ch[u][t[i]]=++cnt;
        u=ch[u][t[i]];
    }
    val[u]++,w[u]++;
}
void dfs(int u)
{
    for(int i=0;i<=1;i++)
        if(ch[u][i])dfs(ch[u][i]),val[u]+=val[ch[u][i]];
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<=n;i++)
    {
        nt=read();
        for(int j=1;j<=nt;j++)t[j]=read();
        ext();
    }
    dfs(0);
    for(int i=1;i<=m;i++)
    {
        ns=read();
        int u=0,f=0,cnt=0;
        for(int j=1;j<=ns;j++)
        {
            s[j]=read();if(f)continue;
            cnt+=w[u];
            if(!ch[u][s[j]]){f=1;}
            else u=ch[u][s[j]];
        }
        if(f)write(cnt);
        else write(val[u]+cnt);
    }
    return 0;
}

4.bzoj4567->技术分享图片

这题听上去很复杂,但是会发现第一种完成方式肯定花费最高,而且存在一种方案能使得不会有第一种方式,那么肯定是不会有第一种方式的了。

根据第二种完成方式会发现对于某个单词,以它为序号最大的后缀的单词越多,这个单词就越该往后放,而且要放在以它为的后缀的单词的前面。

那么就可以将所有单词倒着建Trie树,并且进行路径压缩,使只剩下结束节点。最后的序列就是先走儿子数更少的儿子的dfs序。

#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iomanip>
#include<iostream>
#include<map>
#include<stack>
#include<set>
#include<queue>
#define maxn 510010
#define LL long long
using namespace std;
int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch)&&ch!=‘-‘)ch=getchar();
    if(ch==‘-‘)f=-1,ch=getchar();
    while(isdigit(ch))x=(x<<3)+(x<<1)+ch-‘0‘,ch=getchar();
    return x*f;
}
void write(LL x)
{
    int f=0;char ch[20];
    if(x==0){putchar(‘0‘),putchar(‘
‘);return;}
    if(x<0){putchar(‘-‘),x=-x;}
    while(x)ch[++f]=x%10ll+‘0‘,x/=10ll;
    while(f)putchar(ch[f--]);
    putchar(‘
‘);
}
int siz[maxn],ch[maxn][26],fa[maxn],cnt,tim,dfn[maxn],ns,n;
LL ans;
char s[maxn];
vector<pair<int ,int> >son[maxn];
int gx(char c){return c-‘a‘;}
void ext()
{
    int u=0;
    for(int i=ns;i>=1;i--)
    {
        if(!ch[u][gx(s[i])])ch[u][gx(s[i])]=++cnt;
        u=ch[u][gx(s[i])];
    }
    siz[u]++;
}
void getf(int u,int f)
{
    if(siz[u])fa[u]=f,f=u;
    for(int i=0;i<26;i++)if(ch[u][i])getf(ch[u][i],f);
}
void dfs(int u,int f)
{
    if(u)ans+=(++tim)-f,f=tim;
    sort(son[u].begin(),son[u].end());
    int lim=son[u].size();
    for(int i=0;i<lim;i++)dfs(son[u][i].second,f);
}
int main()
{
    n=read();
    for(int i=1;i<=n;i++)scanf("%s",s+1),ns=strlen(s+1),ext();
    getf(0,0);
    for(int i=cnt;i>=1;i--)if(siz[i])siz[fa[i]]+=siz[i],son[fa[i]].push_back(make_pair(siz[i],i));
    dfs(0,0);
    write(ans);
    return 0;
}

  

5.bzoj1954->技术分享图片

这道题实际上非常简单。因为给出的是一个树且不带修改,很容易可以想到树上的一条路径异或和可以由根两点的路径的异或和相互异或得到(根到lca的异或和会被抵消)。

那么将每个点的值转化为根到这个点路径的异或和,这道题便转化为给出n个数,找出两个数使其异或和最大。(很对劲的太刀流写的)

#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iomanip>
#include<iostream>
#include<map>
#include<stack>
#include<set>
#include<queue>
#define maxn 100010
#define maxm (maxn<<1)
#define maxnd 3000010
using namespace std;
int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)&&ch!=‘-‘)ch=getchar();
	if(ch==‘-‘)f=-1,ch=getchar();
	while(isdigit(ch))x=(x<<3)+(x<<1)+ch-‘0‘,ch=getchar();
	return x*f;
}
void write(int x)
{
	int f=0;char ch[20];
	if(x==0){putchar(‘0‘),putchar(‘
‘);return;}
	if(x<0){putchar(‘-‘),x=-x;}
	while(x)ch[++f]=x%10+‘0‘,x/=10;
	while(f)putchar(ch[f--]);
	putchar(‘
‘);
}
int cnt,fir[maxn],nxt[maxm],v[maxm],w[maxm],ans;
int num[maxn],ch[maxnd][2],n,cntnd; 
void ade(int u1,int v1,int w1){v[cnt]=v1,w[cnt]=w1,nxt[cnt]=fir[u1],fir[u1]=cnt++;}
void getn(int u,int fa)
{
	for(int k=fir[u];k!=-1;k=nxt[k])
		if(v[k]!=fa)num[v[k]]=num[u]^w[k],getn(v[k],u); 
}
void ext(int x)
{
	int u=0;
	for(int i=30;i>=0;i--)
	{
		int tmp=x&(1<<i)?1:0;
		if(!ch[u][tmp])ch[u][tmp]=++cntnd;
		u=ch[u][tmp];
	}
}
int getans(int x)
{
	int u=0,ans=0;
	for(int i=30;i>=0;i--)
	{
		int tmp=x&(1<<i)?1:0;
		if(ch[u][tmp^1])u=ch[u][tmp^1],ans+=(tmp^1)*(1<<i);
		else u=ch[u][tmp],ans+=(tmp)*(1<<i);
	}
	return ans;
}
int main()
{
	memset(fir,-1,sizeof(fir));
	n=read();
	for(int i=1;i<n;i++){int x=read(),y=read(),z=read();ade(x,y,z),ade(y,x,z);}
	getn(1,0);
	for(int i=1;i<=n;i++)ext(num[i]);
	for(int i=1;i<=n;i++)
	{
		int tmp=getans(num[i]);
		ans=max(ans,num[i]^tmp);
	}
	write(ans);
	return 0;
}

以上是关于并不对劲的字符串专题:Trie树的主要内容,如果未能解决你的问题,请参考以下文章

并不对劲的图论专题:SPFA算法的优化

Thematic002.字符串专题

trie树专题

并不对劲的左偏树

专题训练之Trie

一篇并不对劲的后缀自动机教程