洛谷P3294 [SCOI2016]背单词 题解

本文详细解析了洛谷P3294[SCOI2016]背单词的题目,主要讨论如何找到字符串的最优排列以使花费最小。通过将字符串反转并构建Trie树,对树进行重构并排序子节点,利用贪心策略解决此问题。给出的代码实现了这一算法,时间复杂度为O(LlogL),其中L为所有字符串字符之和。
摘要由CSDN通过智能技术生成

洛谷P3294 [SCOI2016]背单词 题解

题目链接:P3294 [SCOI2016]背单词

题意:给定 n n n 个不同的字符串,求一个最优顺序使得花费最小

设当前字符串为 a a a ,位于排列中的 x x x 位置

  • 如果 a a a 存在后缀且 a a a 的后缀在 a a a 之后,花费增加 n 2 n^2 n2
  • 如果 a a a 不存在后缀,则花费增加 x x x
  • y y y a a a 之前与其相距最小的,且是 a a a 的后缀的字符串的位置(前提是 a a a 存在后缀),则花费增加 x − y x-y xy

根据贪心,前两个条件基本上没有用

同时为了方便处理,把所有字符串反转,考虑其前缀。

原问题转化为:

设某个字符串为 a i a_i ai ,位于排列中的 x i x_i xi 位置

  • a i a_i ai 所有前缀必须在 a i a_i ai 之前

  • y i y_i yi a i a_i ai 之前与其相距最小的,且是 a i a_i ai 的前缀的字符串的位置

如果 a a a 不存在前缀,则 y i = 0 y_i=0 yi=0 ,记 v i = x i − y i v_i=x_i-y_i vi=xiyi

给定 n n n 个字符串,求一种排列顺序,使得 ∑ v i \sum v_i vi 最小,求出这个最小值。

考虑建 t r i e \tt{trie} trie 树以存储字符串

然后重构整棵树,只保留「是字符串结尾的结点」,注意根节点也要保留

然后对于每个结点,将其子结点按子树大小从小到大排序

然后跑一遍dfs就好了

重构是为了方便排序子结点。

排序的原因:

设当前结点为 u u u ,则 u u u 的所有子结点都有相同的前缀

这个前缀就是根节点到 u u u 形成的字符串。

对于排序后在后面的子结点,想在排列中距离 u u u 尽可能小

就需要前面的子结点所在子树大小尽可能小,这样他们之间夹着的字符串就少

根据贪心,不难发现这样可以最小化答案

时间复杂度 O ( L log ⁡ L ) O(L \log L) O(LlogL) ,其中 L = ∑ ∣ s i ∣ L=\sum |s_i| L=si

代码:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iomanip>
#include <random>
using namespace std;
#define ll long long
// #define int long long
// #define INF 0x3f3f3f3f3f3f3f3f
#define L (int)(5.1e5+15)
bool cmp(int a,int b);
struct Trie
{
    vector<int> vec[L]; ll res;
    int tot,trie[L][26],ed[L],last[L],sz[L],cnt;
    void insert(string s)
    {
        int u=0;
        for(int i=0; i<s.size(); i++)
        {
            int c=s[i]-'a';
            if(!trie[u][c])trie[u][c]=++tot;
            u=trie[u][c];
        }
        ed[u]=1;
    }
    void rebd(int u)
    {
        if(ed[u]&&u)
        {
            vec[last[u]].push_back(u);
            last[u]=u;
        }
        for(int i=0; i<26; i++)
            if(trie[u][i])
            {
                int v=trie[u][i];
                last[v]=last[u]; rebd(v);
            }
    }
    void dfs(int u)
    {
        sz[u]=1;
        for(int i=0; i<vec[u].size(); i++)
        {
            dfs(vec[u][i]);
            sz[u]+=sz[vec[u][i]];
        }
        sort(vec[u].begin(),vec[u].end(),cmp);
    }
    void solve(int u)
    {
        int dfn=cnt++;
        for(int i=0; i<vec[u].size(); i++)
        {
            res+=cnt-dfn;
            solve(vec[u][i]);
        }
    }
    void main()
    {
        ed[0]=1; rebd(0);
        dfs(0); solve(0);
        cout << res << '\n';
    }
}tr;
bool cmp(int a,int b){return tr.sz[a]<tr.sz[b];}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    int n; string s;
    cin >> n;
    for(int i=1; i<=n; i++)
    {
        cin >> s;
        reverse(s.begin(),s.end());
        tr.insert(s);
    }
    tr.main();
    return 0;
}

转载请说明出处

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值