背单词
Lweb 面对如山的英语单词,陷入了深深的沉思,「我怎么样才能快点学完,然后去玩三国杀呢?」。这时候睿智的凤老师从远处飘来,他送给了 Lweb 一本计划册和一大缸泡椒,然后凤老师告诉 Lweb ,我知道你要学习的单词总共有 (n) 个,现在我们从上往下完成计划表,对于一个序号为 (x) 的单词(序号 (1 ldots x-1) 都已经被填入):
- 如果存在一个单词是它的后缀,并且当前没有被填入表内,那他需要吃 (n imes n) 颗泡椒才能学会;
- 当它的所有后缀都被填入表内的情况下,如果在 (1 ldots x - 1) 的位置上的单词都不是它的后缀,那么他吃 (x) 颗泡椒就能记住它;
- 当它的所有后缀都被填入表内的情况下,如果 (1 ldots x - 1) 的位置上存在是它后缀的单词,所有是它后缀的单词中,序号最大为 (y),那么他只要吃 (x - y) 颗泡椒就能把它记住。
Lweb 是一个吃到辣辣的东西会暴走的奇怪小朋友,所以请你帮助 Lweb,寻找一种最优的填写单词方案,使得他记住这 (n) 个单词的情况下,吃最少的泡椒。
$1 leq n leq 100000 $,所有字符的长度总和 $ 1 leq | ext{len}| leq 510000$
分析
题意理解
https://www.cnblogs.com/f321dd/p/6600264.html
写题面的人脑子打了结吧能写出这种见鬼的描述,逻辑被狗吃了。
第二个条件相当于说:当它的所有后缀都被填入表内的情况下,表内的单词都不是它的后缀。
这句子是人写出来的吗。
然后你必须推导出这句话的意思是:“它的所有后缀”为空集。
而“它的所有后缀”是指是它的后缀的所有单词。
最后把这个题面翻译成人话就是:
-
如果存在一个单词是它的后缀,且当前没被填入,代价为n*n;
-
如果不存在一个单词是它的后缀,代价为x;
-
如果存在一个单词是它的后缀,且已填入的是它后缀的单词中序号最大的为y,代价为x-y。
https://www.cnblogs.com/jklover/p/10216247.html
将所有字符串翻转插入一颗 Trie 树中,则关于后缀的问题全部转化为前缀.
容易发现,我们一定可以避免 1 情况的出现( DAG 图拓扑排序),且避免后代价一定更优.最坏也只有(frac{n(n+1)}{2} < n^2).若在位置 0 加上一个虚拟根作为所有字符串的前缀,那么情况 3 可以看做是 2 的特殊情况,可以一起处理.
将每个字符串作为一个节点,给节点编号,求节点编号与父亲编号差的最小值即可.
贪心地做, dfs 按照子树大小从小到大选出来先编号即可.
贪心证明
首先递归至一个节点的时候,他的儿子应该优先做完,这个可以反证。假设先做其他子树最优,那么若调换一下顺序先做儿子,可以得到更优的解,与假设矛盾,假设不成立,所以优先做完儿子。
然后是优先做size小的儿子,由于要整棵整棵的做,这就相当于排队打水问题,也可以反证,这里不再证明。所以优先做size小的儿子。
时间复杂度
Trie占用了(O(|len|))的时间,dfs贪心的时候要排序,所以是(O(n log_2 n))的时间。总时间复杂度(O(|len| + n log_2 n))。
co int L=51e4+1,N=1e5+1;
vector<int>g[N];
int dfn;
namespace T
{
int tot;
int ch[L][26],val[L];
void ins(char buf[],int n,int v)
{
int u=0;
for(int i=n-1;i>=0;--i)
{
int k=buf[i]-'a';
if(!ch[u][k])
ch[u][k]=++tot;
u=ch[u][k];
}
val[u]=v;
}
void build(int u,int pre)
{
++dfn;
if(val[u])
g[pre].push_back(val[u]),pre=val[u];
for(int i=0;i<26;++i) if(ch[u][i])
build(ch[u][i],pre);
}
}
int siz[N],pos[N];
ll ans;
void dfs(int x)
{
siz[x]=1;
for(int i=0;i<g[x].size();++i)
{
int y=g[x][i];
dfs(y);
siz[x]+=siz[y];
}
}
typedef pair<int,int> pii;
void solve(int x,int fa)
{
pos[x]=++dfn,ans+=pos[x]-pos[fa];
vector<pii>v;
for(int i=0;i<g[x].size();++i)
{
int y=g[x][i];
v.push_back(pii(siz[y],y));
}
sort(v.begin(),v.end());
for(int i=0;i<v.size();++i)
solve(v[i].second,x);
}
char buf[L];
int main()
{
int n=read<int>();
for(int i=1;i<=n;++i)
{
scanf("%s",buf);
T::ins(buf,strlen(buf),i);
}
T::build(0,0);
dfs(0);
solve(0,0);
printf("%lld
",ans);
return 0;
}