Luogu P3294 背单词

这篇具有很好参考价值的文章主要介绍了Luogu P3294 背单词。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

观前须知

本题解使用 CC BY-NC-SA 4.0 许可
同步发布于Luogu题解区。
更好的观看体验 请点这里

笔者的博客主页

正文

Luogu P3294 【SCOI2016】背单词

笔者在刷题的时候看到了这道好题。
花了四十分钟切掉以后,看了一下题解。
发现自己的想法不太一样。
所以想做一篇适合我这样的蒟蒻看的题解。
那么,我们开始吧。

首先:

题意理解

(笔者认为本文最难的一个部分)

给你 \(n\) 个字符串。
要求你找一种这 \(n\) 个字符串的排列使得总花费最小。

规则一:若一个字符串 \(a\) 有一个字符串 \(b\)\(a\) 的后缀,
(这里的后缀在 \(n\) 个字符串中出现过,且不为该串本身,下文同)
\(b\) 排在 \(a\) 前,则花费增加 \(n^2\)

规则二:若 \(a\) 没有后缀,则花费增加 \(a\) 的排名(即 \(x\) )。

规则三:若一个字符串 \(a\) 的所有后缀都排在 \(a\) 前,
则花费增加 \(a\) 到最近一个 \(a\) 的后缀 \(b\) 的距离(即 \(x-y\) )。

那么来简化题意
首先,发现原题中的规则二就是规则三的特例,所以不需要额外考虑。
然后,可以发现规则一增加的 \(n^2\) 实在太多了(每个规则二最多也只能增加 \(n\) 的花费)。
所以不能违反规则一。
即要保证所有字符串的后缀一定排在这个字符串的前面。
(一定存在不违反规则一的方案,按照字符串长度从小到大排序就是一种)

那么题意已经变为了:
在不违反规则一的情况下,
使规则三的花费和最小。

建模

发现不能违反规则一后
规则三中的最近一个后缀变为了长度最大的后缀
发现每个字符串要么有唯一的一个长度最大的后缀,要么没有后缀。
这和的结构类似!
那么我们可以建立一棵树,一个节点的父亲就是它的长度最大的后缀。
(也就是SAM中的后缀树)
对于没有后缀的点,我们建立一个虚根(代码中为0号点),作为它们的父亲。
(这里的虚根可以理解为是一个空串,因为空串是每一个字符串的后缀)

下面给出了一棵后缀树方便大家理解:

a ab ba aab aba ababa bbaab bbbbba

建好这棵树后,我们就可以开始贪心了。

贪心

先直接说贪心策略:
在后缀树上按照dfs序选点,
且每个节点先走子树小的。

(接下来的证明可以感性理解,建议边想边画图)

首先证明dfs序选点是正确的:
对于根节点,它的若干个子节点有若干棵子树。
这里我们考虑其中任意两棵:
我们只需证明,我们要先选完一棵子树,再选择另一棵子树。
不妨设先选的子树的树根为 \(x\),后选的子树的树根为 \(y\)
首先考虑把 \(y\) 提前到 \(x\) 的子树选完前。
\(y\) 提前了 \(a\) 个位置。
对于 \(y\) 子树内的第一个子节点,花费增加了 \(a\)
对于插入 \(y\) 后的第一个节点,花费增加了 \(1\)
其余节点花费不变。
继续把 \(y\) 的子树内的节点提前,花费不变。
所以对于根来说,选完一棵子树后再选另一棵是最优的。
递归下去可以证明dfs序选点是最优的。

接下来证明要先走子树小的:
对于一个节点,考虑它的每一个孩子,
发现可以递归处理每一个孩子的子树内的节点,这样只需要考虑每个子树的根节点,
也就是它的每一个孩子,到它本身的距离
根据上面的结论可得,每个孩子到这个节点的距离就是在遍历到这个孩子前已经走过的节点数
那么为了距离和最小,显然要已经走过的节点数尽量小
所以子树小的优先选

好的,那么我们的答案就可以由贪心策略算出来了。
欸?你问我是不是少了些什么?
好吧,
最后一部分:

建树

为了建树,我们只需要求出每个节点的父节点,
即每个字符串的最长后缀。
我们先根据字符串长度从小到大排序。
那么每个字符串的后缀都在这个字符串前面了。
但是后缀不好做,
所以我们把每个字符串都倒过来变成前缀。
我们把每个字符串依次倒叙插入到Trie树中。
并在每个字符串的终止结点记录编号。
我们可以惊喜地发现:
对于一个字符串的最长后缀(这里已经变为前缀了),
就是在这个字符串在Trie树上的对应路径中,
深度最大的终止节点。
那么我们就能很容易地求出每个节点的父亲。
那么就可以建树了。
(这块讲的比较抽象,建议配着代码食用,或自己think一下)

一些小细节:
用vector存树方便sort
按照字符串长度排好的顺序其实是后缀树的拓扑逆序,可以直接倒序枚举更新sz
因为字符串长度不确定所以不能用 scanf 和 char 数组了(悲)

这份代码最短用时223ms,拿了个次优解直接开润~

#include<bits/stdc++.h>

using namespace std;

static constexpr int AwA = 1e5 + 10;
static constexpr int PwP = 6e5 + 10;

int n;
//因为这道题每个字符串长度不确定,所以我只能抛弃我的char数组了(悲)
string s[AwA];
//记录每个节点算出来的父亲
int fa[AwA];
//字典树,id[u]!=0时记录该节点对应的字符串编号
int ch[PwP][26], id[PwP], tot = 1;

vector<int> tr[AwA];
int sz[AwA];
long long ans;

//贪心选点
void Dfs(int u) {
    int cur = 1;
    for (int v: tr[u]) {
        Dfs(v);
        ans += cur;
        cur += sz[v];
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);

    cin >> n;
    for (int i = 1; i <= n; i++) cin >> s[i];
    //根据字符串长度排序
    sort(s + 1, s + n + 1, [](auto &s1, auto &s2) { return s1.size() < s2.size(); });

    int u, p;
    for (int i = 1; i <= n; i++) {
        //如果路径上没有终止节点,即没有后缀,则父亲为虚根0
        fa[i]=0;
        u = 1;
        //倒叙枚举,变后缀为前缀
        for (auto k = s[i].rbegin(); k != s[i].rend(); k++) {
            p = *k - 'a';
            if (!ch[u][p]) ch[u][p] = ++tot;
            u = ch[u][p];
            //遇到终止节点更新父亲
            if (id[u]) fa[i] = id[u];
        }
        //记录终止节点
        id[u] = i;
    }

    //因为父亲串的长度一定小于儿子,所以根据字符串长度排序后为拓扑逆序
    for (int i = n; i; i--) sz[i]++, sz[fa[i]] += sz[i];
    //建树
    for (int i = 1; i <= n; i++) tr[fa[i]].push_back(i);
    //按子树大小排序,方便贪心选择
    //注意0节点也要排序
    auto cmp = [&](int i, int j) { return sz[i] < sz[j]; };
    for (int i = 0; i <= n; i++) sort(tr[i].begin(), tr[i].end(), cmp);
    Dfs(0);
    printf("%lld\n", ans);
    return 0;
}

希望这篇题解能帮助你更好地理解这道很好的贪心题。
完结撒花!~文章来源地址https://www.toymoban.com/news/detail-844362.html

到了这里,关于Luogu P3294 背单词的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【string题解 C++】字符串相乘 | 翻转字符串III:翻转单词

    目录 字符串相乘 题面 错误记录 Way1 拆分成“先乘后加” 思路 实现 时空复杂度分析 反思 Way2 用数组 思路 实现 时空复杂度分析 翻转字符串III:翻转字符串中的单词 题面 错误记录 思路1 遍历找单词 实现 思路2 暴力解法 实现 力扣(LeetCode)官网 - 全球极客挚爱的技术成长平

    2024年02月07日
    浏览(63)
  • C++ | Leetcode C++题解之第30题串联所有单词的子串

    题目: 题解:

    2024年04月15日
    浏览(49)
  • 计算机程序安装及使用须知_kaic

       本文件夹中的“PowerDesigner建模”目录下包含三个可运行文件TMS1.cdm,TMS.cdm,TMS.pdm分别为TMS系统的实体关系简图、实体关系图和数据库模型,使用PowerDesigner集成开发环境打开任意一个文件即可运行。  本安装说明以Microsoft SQL Server 2000中文开发版为例来阐述的,对于Micros

    2023年04月25日
    浏览(47)
  • Luogu P3007 奶牛议会

    本题解使用 CC BY-NC-SA 4.0 许可 。 同步发布于 Luogu 题解区。 更好的观看体验 请点这里 。 笔者的博客主页 Luogu P3007 【USACO11JAN】 The Continental Cowngress G 前置知识: 2-SAT 、 Tarjan 。 (应该没有人会 2-sat 不会 tarjan 吧) 这道题除去输出 \\\'?\\\' 的部分是一道 2-SAT 的纯版子题。 其他题解

    2024年04月12日
    浏览(32)
  • 【Luogu】 P5445 [APIO2019] 路灯

    点击打开链接 转化很妙 考虑关路灯 x x x 的操作 令左边第一个未关的路灯为 L L L ,右边第一个未关的路灯为 R R R ,那么这一次会影响的区间即为 l ∈ [ L + 1 , x ] ,    r ∈ [ x , R − 1 ] lin[L+1,x],;rin[x,R-1] l ∈ [ L + 1 , x ] , r ∈ [ x , R − 1 ] ,既然有 2 个限制,那么我们把限制

    2024年02月10日
    浏览(74)
  • 【Luogu】 P6076 [JSOI2015]染色问题

    点击打开链接 可以一个一个条件考虑 只考虑条件 1 1 1 答案即为 ( c + 1 ) n m (c+1)^{nm} ( c + 1 ) nm 考虑条件 1 , 2 1,2 1 , 2 对每一行的方案数减去 1 1 1 答案即为 ( ( c + 1 ) m − 1 ) n ((c+1)^m-1)^n (( c + 1 ) m − 1 ) n 考虑条件 1 , 2 , 3 1,2,3 1 , 2 , 3 考虑容斥 容斥至少有 i i i 列未被染色,即为

    2024年02月09日
    浏览(53)
  • 【LuoGU 1273】有线电视网——树上分组背包问题

    某收费有线电视网计划转播一场重要的足球比赛。他们的转播网和用户终端构成一棵树状结构,这棵树的根结点位于足球比赛的现场,树叶为各个用户终端,其他中转站为该树的内部节点。 从转播站到转播站以及从转播站到所有用户终端的信号传输费用都是已知的,一场转播

    2024年02月16日
    浏览(40)
  • Redis常见须知

    Redis 是一种基于内存的数据库,对数据的读写操作都是在内存中完成,因此 读写速度非常快,常用于缓存,消息队列、分布式锁等场景 。 Redis 提供了多种数据类型来支持不同的业务场景,比如 String(字符串)、Hash(哈希)、 List (列表)、Set(集合)、Zset(有序集合)、Bitmaps(位图)

    2024年02月16日
    浏览(31)
  • AIGC入门须知

    布道 AI ,让更多普通人意识到新时代已经到来,毕竟早人一步就是红利。 GPT 是一种自然语言处理技术的聊天机器人,它能够实现智能对话、回答用户提问、完成任务等功能。 具体来说,GPT 能够通过学习语言模式、理解语义等方式,生成流畅自然的回答,并提供高度个性化

    2024年02月11日
    浏览(34)
  • luogu_P1040 [NOIP2003 提高组] 加分二叉树

    P1040 [NOIP2003 提高组] 加分二叉树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题意:给你一颗中序遍历为1到n的二叉树,和每个节点的val。树的值=左子树的值×右子树的值+根的val,空树值为1,求整个树最大值和这个值树的前序遍历。 题解:区间dp。dp[l][r]表示最大值,root[l][

    2023年04月27日
    浏览(77)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包