首页 > 其他分享 >Luogu P3294 背单词

Luogu P3294 背单词

时间:2024-04-03 16:46:58浏览次数:16  
标签:子树 后缀 Luogu 规则 节点 int P3294 字符串 背单词

观前须知

本题解全部内容遵循CC BY-NC-SA 4.0 Deed原则
同步发布于Luogu题解区
更好的观看体验 点这里
笔者的博客主页

正文

Luogu P3294 【SCOI2016】背单词

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

首先

题意理解

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

给你 \(n\) 个字符串
要求你找一种这 \(n\) 个字符串的排列
规则一:若一个字符串 \(a\) 有一个字符串 \(b\) 为 \(a\) 的后缀,
(这里的后缀在 \(n\) 个字符串中出现过,且由题意不为该串本身,下文同理)
且 \(b\) 排在 \(a\) 前,则花费增加 \(n^2\)
规则三:若一个字符串 \(a\) 的所有后缀都排在 \(a\) 前,
则花费增加 \(a\) 到最近一个 \(a\) 的后缀 \(b\) 的距离(即 \(x-y\) )
规则二:特别地,若 \(a\) 没有后缀,则花费增加 \(a\) 的排名(即 \(x\) )
求最小花费

(吐槽一下,原题真的很不好理解,笔者这里看了十分钟都以为是题目给定了排列顺序)

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

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

建模

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

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

a ab ba aab aba ababa bbaab bbbbba
alt text

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

贪心

先直接说贪心策略:
在后缀树上按照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;
}

希望这篇题解能帮助你更好地理解这道很好的贪心题
完结撒花!~

标签:子树,后缀,Luogu,规则,节点,int,P3294,字符串,背单词
From: https://www.cnblogs.com/Sugar-Cube/p/18113015

相关文章

  • 英语背单词 专四词汇 2024年04月 ChatGPT
    2024-04-03  2024-04-02  2024-04-01IndexWordPronunciationPartsofSpeechExplanationTranslationinChinese1insulationɪnsəˈleɪʃənnounMaterialorsubstanceusedtopreventheat,electricity,orsoundfrompassing绝缘;隔热材料2......
  • luogu P1543 [POI2004] SZP 题解
    题目传送门前置知识树形DP解法将\(a_{i}\)向\(i\)连一条有向边,这样就形成了基环外向树森林。基环外向树森林内每棵基环外向树是相互独立的,需要单独处理。对于每棵基环外向树,任取环上一点\(x\),断开\(x\)到\(fa_{x}\)的有向边,外向树就变成了一棵以\(x\)为根的树。......
  • Luogu P6834 梦原 题解
    原题传送门首先考虑如果树的形态确定了之后的情况:如果当前点比爸爸的值大,那么显然爸爸变成\(0\)之后,这个点需要自己被额外操作删除,贡献就是\(a[v]-a[u]\)。类似,如果比爸爸的值小,那么这个点肯定会跟爸爸一起被删除,所以贡献就是\(0\)。综上所述,一个点的贡献是\(max{a[v]-......
  • luogu6801题解
    本题解中不区别长度和宽度的区别,它们在本题解中指的是同一个东西。本题解做法用到单调栈。看这个特殊性质好像是让我们在高度上进行研究一下。子任务\(4\)的特殊性质是想让你搞明白在一个矩形中如何计算其内部的矩形个数。下面是一个\(4\times5\)大小的矩形。先来不考......
  • luoguP3330 [ZJOI2011] 看电影--组合数学--高精度
    \(luoguP3330\)[ZJOI2011]看电影废了老命想题解$$luogu$$$$HZOI$$题意到了难得的假期,小白班上组织大家去看电影。但由于假期里看电影的人太多,很难做到让全班看上同一场电影。最后大家在一个偏僻的小胡同里找到了一家电影院,但这家电影院分配座位的方式很特殊,具体方式如......
  • Luogu P1220 关路灯 题解 [ 蓝 ][ 区间dp ]
    关路灯题目描述某一村庄在一条路线上安装了\(n\)盏路灯,每盏灯的功率有大有小(即同一段时间内消耗的电量有多有少)。老张就住在这条路中间某一路灯旁,他有一项工作就是每天早上天亮时一盏一盏地关掉这些路灯。为了给村里节省电费,老张记录下了每盏路灯的位置和功率,他每次关......
  • 英语背单词 专四词汇 2024年03月 ChatGPT
     2024-03-01indexwordpronunciationpartsofspeechexplanationtranslationinChinese1inert/ɪˈnəːt/adjectiveLackingtheabilitytomoveorreact;inactive.惰性的;不活跃的2anticipation/ænˌtɪsɪˈpeɪʃən/nounTheactoflookingfo......
  • 【题目】LuoguP1065
    准备机试,做两道题复健(这事我是不是干了好多次了)。https://www.luogu.com.cn/problem/P1065题意是有n个工件,每个工件有m道工序,每个工件的每道工序有其用时,同时对应一台机器,机器总共也有m台。每台机器同时只能处理一个工序。现给出工件的工序顺序,问尽可能靠前安排,所用的时间。 ......
  • 【不背单词】生词本单词导出
    原文持续更新中:https://www.cnblogs.com/MrFlySand/p/18024734操作流程点击我-安装打开【不背单词】-【我的生词】开启程序点击【导出】(注:不用显示所有的单词也可进行导出)效果图点击链接,与我一起学习https://pd.qq.com/s/c01padly......
  • 2024.2.20闲话——luogu5021随机选取边的正确性
    推歌:生きる(feat.可不)——水野あつ这首歌听完之后接着转去咖喱乌冬了,歌词甚至能接上,没绷住。刚刚写证明中间把wxy的杯子碰倒洒键盘上了,抢救键盘的过程中碰到缝就有水,有点涩。但是这个键盘里面怎么这么脏啊?下面来证luogu5021在菊花树中以任何顺序选取边并采取贪心策略的......