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
原文地址:https://www.cnblogs.com/lyttt/p/12885623.html