【SCOI2016】背单词
P3294【SCOI2016】背单词
【提示】
这道题大概是告诉我们,让我们用一堆n个单词安排顺序,如果当前位置为x,当前单词的后缀没在这堆单词出现过,代价就为x,这里的后缀是原意,但不算自己(不算本身的后缀【如果用集合来说就是真子集】),举个例子比如abc的后缀是bc和c但是它的后缀不包括它本身。
否则如果它的后缀(指在n个单词中的)在1~x-1全部出现了,代价为x减去最后一个后缀的位置y,如果没有全部出现,代价就为n^2,我们可以很显然的发现这个吃泡椒的数量是根据这个后缀来决定的,所以我们要尽量将所有的背当前的单词吃的泡椒数目变得最小时,吃泡椒的总数就会最小,那么我们就需要让背当前的单词时,吃的泡椒的类型是y-x类型的,因为只有这样才能满足最小。
我们可以发现按后缀建字典树,然后直接按子树大小贪心使其被每一个单词吃的泡椒数最少,然后直接一个贪心+字典树就可以过了这道题。
但是我一开始偷懒就直接在字典树上贪心走子树,这样是不行的,吃泡椒的数量的大小是错误的,我们得把关键点树给建出来然后再做,只有这样我们才能将这题A掉。
题解
直接来讲正确的解法:
首先可以发现一定要保证放每个点前它的后缀一定已经被放了(可以根据之前说的要使每个单词的吃的泡椒数最少【贪心的思路】来理解这个东西),所以我们可以根据这个把所有的串倒着插入我们建的字典树当中,然后答案一定是类似于这个样子的:一个串->若干以这个串为后缀的串。
那么按照怎样的插入顺序插入会更优呢? 可以发现先插入儿子较少的串答案会更优 那么我们就只需要把这棵字典树重构一下,去掉所有的无效节点,只留下代表某个点的结尾的节点 然后每次都是贪心的按照儿子从少到多的节点的顺序Dfs即可,这样就行了。
#include <bits/stdc++.h>//万能头文件 #define ll long long//宏定义long long using namespace std;//运用命名空间std const int N=6e5+10;//根据数据范围给出n的最大值 vector <int> Edge[N];//定义一个可变的数组 char s[N]; int ch[N][26],endro[N],tot; int n,clock=-1,siz[N]; ll ans; void ins()//初始化,将字母转换成数字存储 { scanf("%s",s+1);//下标从一开始 int now=0,len=strlen(s+1); for(int i=len;i;i--) { int c=s[i]-'a'; if(!ch[now][c]) ch[now][c]=++tot; now=ch[now][c]; } endro[now]=1; } void dfs0(int now,int anc) { if(endro[now]) Edge[anc].push_back(now),anc=now; for(int i=0;i<26;i++) if(ch[now][i]) dfs0(ch[now][i],anc); } void dfs(int now)//按照儿子从少到多的节点的顺序的贪心的Dfs { siz[now]=1; for(int i=0;i<Edge[now].size();i++) dfs(Edge[now][i]),siz[now]+=siz[Edge[now][i]]; } bool cmp(int x,int y){return siz[x]<siz[y];} void dfs(int now,int las) { int tim=++clock; ans=ans+tim-las; std::sort(Edge[now].begin(),Edge[now].end(),cmp); for(int i=0;i<Edge[now].size();i++) dfs(Edge[now][i],tim); } int main()//主函数 { scanf("%d",&n);//n个单词 for(int i=1;i<=n;i++) ins(); dfs0(0,0); dfs(0); dfs(0,0); printf("%lld\n",ans);//输出吃泡椒的总数 return 0;//结束程序 }
浙公网安备 33010602011771号