1. 程式人生 > >並不對勁的字符串專題(三):Trie樹

並不對勁的字符串專題(三):Trie樹

digi .cn height ini width trie樹 轉化 def 而且

據說這些並不對勁的內容是《信息學奧賽一本通提高篇》的配套練習。

並不會講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(‘\n‘);return;}
    if(x<0){putchar(‘-‘),x=-x;}
    while(x)ch[++f]=x%10+‘0‘,x/=10;
    while(f)putchar(ch[f--]);
    putchar(‘\n‘);
}
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(‘\n‘);return;}
    if(x<0){putchar(‘-‘),x=-x;}
    while(x)ch[++f]=x%10+‘0‘,x/=10;
    while(f)putchar(ch[f--]);
    putchar(‘\n‘);
}
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(‘\n‘);return;}
    if(x<0){putchar(‘-‘),x=-x;}
    while(x)ch[++f]=x%10ll+‘0‘,x/=10ll;
    while(f)putchar(ch[f--]);
    putchar(‘\n‘);
}
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(‘\n‘);return;}
	if(x<0){putchar(‘-‘),x=-x;}
	while(x)ch[++f]=x%10+‘0‘,x/=10;
	while(f)putchar(ch[f--]);
	putchar(‘\n‘);
}
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樹