把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

SCOI2016 背单词【Trie树,贪心】

题目链接

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

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

 


 

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

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

既然是要符合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\n",ans);
78     return 0;
79 }
Code
复制代码

 

posted @   Starlight_Glimmer  阅读(168)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
浏览器标题切换
浏览器标题切换end
点击右上角即可分享
微信分享提示