P3294 [SCOI2016]背单词

传送门

贪心+字典树

很显然,填一个单词前肯定要优先把所有是它后缀的单词填掉

考虑怎么判后缀

可以把单词倒过来,加入字典树

那么填一个单词前要先填掉它所有祖先的单词

即要从深度小的填到深度大的

可以按搜索顺序填

 

考虑要怎样确定搜索顺序

随便画一颗树

发现深度优先比广度优先更优

在深度优先时 单词少的子树优先更优

所以可以把每个节点所有的儿子按子树大小排一遍序

然后深搜,更新答案

估了一波复杂度好像可以接受

WA了6个点...

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const int N=510007;
int n,tot,len;
long long ans;
char s[N];
inline void read_s()
{
    len=0;
    char c=getchar();
    while(c<'a'||c>'z') c=getchar();
    while(c>='a'&&c<='z')
    {
        s[len++]=c;
        c=getchar();
    }
}
struct node
{
    int sz,id;
}nex[N][27];
inline bool cmp(const node &a,const node &b){ return a.sz<b.sz; }
int ch[N][27],cnt,sz[N];//sz是每个节点的子树大小
bool pd[N];//结束标记
int dfs1(int x)
//第一遍深搜处理搜索顺序
{
    for(int i=1;i<=26;i++)
    {
        if(!ch[x][i]) continue;
        nex[x][i].sz=dfs1(ch[x][i]);//遍历儿子
        nex[x][i].id=i;
        sz[x]+=nex[x][i].sz;//更新sz
    }
    sort(nex[x]+1,nex[x]+27,cmp);//对儿子按sz排序
    return sz[x];
}
void dfs2(int x,int las)
//las是最近的有结束标记的祖先
{
    if(pd[x])//如果有结束标记
    {
        tot++;
        ans+=(tot-las);//更新答案
        las=tot;
    }
    for(int i=1;i<=26;i++)
    {
        int v=nex[x][i].id;
        if(!ch[x][v]) continue;
        dfs2(ch[x][v],las);//搜下去
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        read_s();
        int u=0;
        for(int j=len-1;j>=0;j--)
        {
            int v=s[j]-'a'+1;
            if(!ch[u][v]) ch[u][v]=++cnt;
            u=ch[u][v];
            if(!j) pd[u]=sz[u]=1;
        }//建立字典树
    }
    dfs1(0);

    dfs2(0,0);
    cout<<ans;
    return 0;
}
WAWAWA

 

调试半天发现是自己算法的问题...

如果是这样一颗树:

 

那么我深搜的顺序就是先向右边两个跑

右边跑完了再往左边跑

但是应该先往最左边的一个跑....

解决方法很简单

把中间没用的白色节点去掉

把根和蓝色节点的关系拿出来重新建一颗树

再按子树大小一个个深搜

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
using namespace std;
const int N=510007;
int n,tot,len;
long long ans;
char s[N];
inline void read_s()
{
    len=0;
    char c=getchar();
    while(c<'a'||c>'z') c=getchar();
    while(c>='a'&&c<='z')
    {
        s[len++]=c;
        c=getchar();
    }
}
int ch[N][27],cnt,sz[N],f[N];
bool pd[N];
inline bool cmp(const int &a,const int &b){ return sz[a]<sz[b]; }
vector <int> v[N];//新图用vector存很方便
void rebuild(int x,int fx)//重建
{
    int t= pd[x] ? x : fx;
    for(int i=1;i<=26;i++)
    {
        int u=ch[x][i]; if(!u) continue;
        rebuild(u,t);
    }
    if(pd[x])
        v[fx].push_back(x);
}
void prework(int x)//对儿子的顺序排序
{
    int l=v[x].size();
    for(int i=0;i<l;i++)
    {
        int u=v[x][i];
        prework(u);
        sz[x]+=sz[u];
    }
    sort(v[x].begin(),v[x].end(),cmp);//骚操作
}
void dfs(int x,int fa)//深搜更新答案
{
    if(x) f[x]=++tot;
    ans+=(f[x]-f[fa]);
    int l=v[x].size();
    for(int i=0;i<l;i++)
        dfs(v[x][i],x);
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        read_s();
        int u=0;
        for(int j=len-1;j>=0;j--)
        {
            int x=s[j]-'a'+1;
            if(!ch[u][x]) ch[u][x]=++cnt;
            u=ch[u][x];
            if(!j) pd[u]=sz[u]=1;
        }//建字典树
    }

    rebuild(0,0);

    prework(0);

    dfs(0,0);
    cout<<ans;
    return 0;
}

 

posted @ 2018-09-16 09:26  LLTYYC  阅读(242)  评论(0编辑  收藏  举报