zoukankan      html  css  js  c++  java
  • SCOI2016 背单词【Trie树,贪心】

    题目链接

    首先吐槽一波原题意,描述地太不清楚了,还是出题人想要出语文断句题?

    题目链接是团队考试的题,题意重置版。


    显然我们要避免那种$i*i$的情况,因为这样非常不划算,($i^2>=i>i-j$)。那我们来看看能不能安排一个合法的顺序来规避这个情况。显然是可以的,因为如果我们按照后缀关系连边,它会形成一个$DAG$,不可能产生冲突,我们的安排只要符合这个$DAG$的递推关系就可以规避$i*i$的情况。

    在这里,没有后缀的情况可以视为后缀是空串,这样就可以把所有情况统一起来,接下来我们就只讨论剩下的这一种情况。

    既然是要符合$DAG$所表示的关系,那么对于一个单词$i$,我们把单词$i$和他的最长后缀$j$连边,由于一个单词的最长后缀是唯一确定的,而一个单词可以同时是很多个单词的后缀,所以他们的关系大概是长这个样子的:

    但是这东西看起来很别扭,我们可以改一改,反转一下边,就非常优美了:

    如果反过来的话,就相当于父亲是儿子的最长后缀。对于一个单词的记忆时间,就是它自己被记忆的顺序号减去自己的父亲被记忆的顺序号,即父子被访问的时间间隔差。

    相当于我们要给这棵树确定一个访问节点的顺序,一个点只有在他的父亲被访问之后才能被访问,而且访问顺序不一定要按照$dfs$的顺序来,可以跳着访问,比如$u$的四个儿子:$v1$,$v2$,$v3$,$v4$,我可以挨着访问$v1$,$v2$,$v3$,$v4$,然后再去访问$v1$的儿子。但是这样却一定没有按照$dfs$的顺序优,因为$v1$,$v2$,$v3$的儿子们和它的父亲(也就是$v1$,$v2$,$v3$)之间相隔的时间变长了。

    所以按照$dfs$的顺序访问最优。而兄弟之间的访问顺序,则应该让子树大小比较小的儿子先遍历会更优,因为在选择兄弟之间的顺序时,他们的父亲已经被访问,也就是父亲的访问时间已经确定,选子树小的可以使它的兄弟们的更大的子树等待的时间更少(类比排队接水的那个贪心)。

    那么现在最关键的问题就是怎么求出我们上面的那棵树,这棵树父亲是儿子的最长后缀,如果把串反过来(就变成了父亲是儿子的最长前缀)只需要一棵$Trie$,把相邻的单词节点连边就可以解决了。

    代码非常小巧清新。

     1 #include<cstdio>
     2 #include<algorithm>
     3 #include<vector>
     4 #include<cstring>
     5 using namespace std;
     6 #define N 100000
     7 #define M 510000
     8 #define INF 0x3f3f3f3f
     9 #define MOD 1000000007
    10 #define LL long long
    11 int rd()
    12 {
    13     int x=0,f=1;char c=getchar();
    14     while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
    15     while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48); c=getchar();}
    16     return f*x;
    17 }
    18 char s[M];
    19 int ch[M][26],cnt,ed[M];
    20 vector<int>G[M];
    21 int siz[M],tot;
    22 LL ans;
    23 bool cmp(int x,int y)
    24 {
    25     return siz[x]<siz[y];
    26 }
    27 void Insert()
    28 {
    29     int len=strlen(s+1),u=0;
    30     for(int i=len;i>=1;i--)
    31     {
    32         int x=s[i]-'a';
    33         if(!ch[u][x]) ch[u][x]=++cnt;
    34         u=ch[u][x];
    35     }
    36     ed[u]=1;
    37 }
    38 void dfs1(int u,int fa)
    39 {
    40     if(ed[u]==1) G[fa].push_back(u),fa=u;
    41     for(int i=0;i<26;i++)
    42         if(ch[u][i])
    43             dfs1(ch[u][i],fa);
    44 }
    45 void dfs2(int u)
    46 {
    47     siz[u]=1;
    48     for(int i=0;i<G[u].size();i++)
    49     {
    50         int v=G[u][i];
    51         dfs2(v);
    52         siz[u]+=siz[v];
    53     }
    54 }
    55 void solve(int u,int lst)
    56 {
    57     int t=++tot;
    58     ans+=t-lst;
    59     sort(G[u].begin(),G[u].end(),cmp);
    60     for(int i=0;i<G[u].size();i++)
    61     {
    62         int v=G[u][i];
    63         solve(v,t);
    64     }
    65 }
    66 int main()
    67 {
    68     int n=rd();
    69     for(int i=1;i<=n;i++)
    70     {
    71         scanf("%s",s+1);
    72         Insert();
    73     }
    74     dfs1(0,0);
    75     dfs2(0); 
    76     solve(0,1);
    77     printf("%lld
    ",ans);
    78     return 0;
    79 }
    Code
  • 相关阅读:
    常用的一些js方法
    git常用命令
    thread join和detach的区别
    C和C++的区别和联系
    C++面试集锦( 面试被问到的问题 )
    C/C++面试题:编写类String的构造函数、析构函数和赋值函数。
    C++ 多态的实现及原理
    获取当前操作系统的ip
    CString的头文件
    C++ 多用户模式下同一个exe只能运行一次
  • 原文地址:https://www.cnblogs.com/lyttt/p/12885623.html
Copyright © 2011-2022 走看看