弦论:本来不想写板子题题解的,但奈何这道题的题解都太垃圾了,导致我理解了一个晚上都没想明白 dp 转移啥意思/fn/fn/fn,所以记录一下。

思路

\(t=0\) 时

考虑 SAM 思路,建出后缀自动机后,该字符串内所有本质不同的子串都可以用一条从根到某节点的路径表示出来,且不可能表示出其他任何非子串的字符串。

那么我们就可以进行类似平衡树查找的操作,每次先选择一个字典序更小的节点,判断进去后有没有这么多的子串,然后利用 dfs 输出方案即可。

那么进入一个节点后能构成的子串数如何计算呢?根据前面的性质,不难发现它就是从该节点出发,到任意一个其他节点的方案数。

于是我们就可以在 DAG 上拓扑了,由于 SAM 的优良性质,所以它的转移边组成的图一定是一个 DAG。

定义 \(dp_i\) 表示走到 \(i\) 前的路径已确定,接着走的方案数。转移方程如下:

\[dp_i=1+\sum_{j=1}^{|v_i|}dp_{v_{i,j}}
\]

为啥要加那个 \(1\) 呢?因为它可以在当前节点停下,后面不走了。也因此在 dfs 进入一个节点时,要先把停留在该节点的方案数减掉。如果剩余的方案数小于等于 \(0\),就说明走完了。

同时,走到 \(i\) 前的路径已确定的条件也很重要,下文会有提及。

时间复杂度 \(O(n|\sum|)\),瓶颈在于构建自动机和 dfs。

\(t=1\) 时

考虑沿用上一问的思路,这次由于子串能重复计算,所以尝试修改 dp 的转移。

我们先求出每个节点的 endpos 集合的字符串会出现多少次,假设这个值为 \(w_i\)。显然一个 endpos 集合内子串的出现次数应该是相同的。于是在后缀链接树上树形 dp 一遍即可。

那么我们回到 dp 状态定义中“走到 \(i\) 前的路径已确定”的条件,可以写出如下的转移:

\[dp_i=w_i+\sum_{j=1}^{|v_i|}dp_{v_{i,j}}
\]

为什么一定要保证前面的路径确定呢?如果前面的路径不确定,那么 \(i\) 处的 endpos 中所有的字符串都有可能成为当前的状态,那么 \(w_i\) 就要乘 endpos 的大小了,这显然是不符合题意的,因为 dfs 时走到 \(i\) 的路径已经被定下来了,对应着的是 endpos 里唯一的一个字符串。

\(w_i\) 的实际含义是把以 \(i\) 为结尾的子串个数算进 dp 值中,因此要加上它的出现次数。

同理,dfs 时减去的就不是 \(1\),而是 \(w_u\) 了。

时间复杂度 \(O(n|\sum|)\)。

注意 \(w_1\) 设为 \(0\),因为空串是不能计入其中的。

代码

#include <bits/stdc++.h>
#define fi first
#define se second
#define lc (p<<1)
#define rc ((p<<1)|1)
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi=pair<int,int>;
int n,t,tp,rk,ch[1000005][26],fa[1000005],tot=1,np=1,len[1000005],rd[1000005];
ll w[1000005],dp[1000005];
char s[1000005];
vector<int>g[1000005],g2[1000005];
void extend(int c)
{
int p=np;
np=++tot;
len[np]=len[p]+1;w[np]=1;
for(;p&&ch[p][c]==0;p=fa[p])ch[p][c]=np;
if(p==0)fa[np]=1;
else
{
int q=ch[p][c];
if(len[q]==len[p]+1)fa[np]=q;
else
{
int nq=++tot;
len[nq]=len[p]+1;
fa[nq]=fa[q],fa[q]=nq,fa[np]=nq;
for(;p&&ch[p][c]==q;p=fa[p])ch[p][c]=nq;
memcpy(ch[nq],ch[q],sizeof(ch[q]));
}
}
}
void dfs2(int u)
{
for(auto v:g2[u])
{
dfs2(v);
w[u]+=w[v];
}
}
void init()
{
if(tp==0)
{
for(int i=2;i<=tot;i++)w[i]=1;
w[1]=0;
}
else
{
for(int i=2;i<=tot;i++)g2[fa[i]].push_back(i);
dfs2(1);
w[1]=0;
}
queue<int>q;
for(int i=1;i<=tot;i++)
{
for(int j=0;j<26;j++)
{
int v=ch[i][j];
if(v)
{
rd[i]++;
g[v].push_back(i);
}
}
}
for(int i=1;i<=tot;i++)
{
if(rd[i]==0)q.push(i);
dp[i]=w[i];
}
while(!q.empty())
{
int u=q.front();
q.pop();
for(auto v:g[u])
{
if(v)
{
rd[v]--;
if(rd[v]==0)q.push(v);
dp[v]+=dp[u];
}
}
}
}
void dfs(int u,int rk)
{
rk-=w[u];
if(rk<=0)return;
for(int i=0;i<26;i++)
{
int v=ch[u][i];
if(v==0)continue;
if(rk<=dp[v])
{
cout<<char(i+'a');
dfs(v,rk);
return;
}
rk-=dp[v];
}
}
void solve()
{
if(rk>dp[1])
{
cout<<-1<<'\n';
return;
}
dfs(1,rk);
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>s+1;
n=strlen(s+1);
for(int i=1;i<=n;i++)extend(s[i]-'a');
cin>>tp>>rk;
init();
t=1;
while(t--)solve();
return 0;
}

Luogu P3975 TJOI2015 弦论 题解 [ 紫 ] [ 后缀自动机 ] [ 动态规划 ] [ 拓扑排序 ]的更多相关文章

  1. 洛谷 P3975 / loj 2102 [TJOI2015] 弦论 题解【后缀自动机】【拓扑排序】

    后缀自动机入门. 题目描述 为了提高智商,ZJY 开始学习弦论. 这一天,她在<String theory>中看到了这样一道问题:对于一个给定的长度为 \(n\) 的字符串,求出它的第 \ ...

  2. luogu P3975 [TJOI2015]弦论 SAM

    luogu P3975 [TJOI2015]弦论 链接 bzoj 思路 建出sam. 子串算多个的,统计preant tree的子树大小,否则就是大小为1 然后再统计sam的节点能走到多少串. 然后就 ...

  3. Luogu P3975 [TJOI2015]弦论

    题目链接 \(Click\) \(Here\) 题目大意: 重复子串不算的第\(k\)大子串 重复子串计入的第\(k\)大子串 写法:后缀自动机. 和\(OI\) \(Wiki\)上介绍的写法不太一样 ...

  4. 「2017 山东一轮集训 Day5」字符串 (后缀自动机, 拓扑排序)

    /** 首先通过SAM求出每个串本质不同的子串 然后发现转移不好处理整体的本质不同 形如串A可能状态有a,b,ab,空,串B可能状态有b,空两种, 那么我们需要处理ab + 空 和 a + b的情况 ...

  5. 洛谷 P3975 [TJOI2015]弦论 解题报告

    P3975 [TJOI2015]弦论 题目描述 为了提高智商,ZJY开始学习弦论.这一天,她在<String theory>中看到了这样一道问题:对于一个给定的长度为\(n\)的字符串,求 ...

  6. 2021.07.17 题解 CF1385E Directing Edges(拓扑排序)

    2021.07.17 题解 CF1385E Directing Edges(拓扑排序) CF1385E Directing Edges - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) ...

  7. BZOJ3998:[TJOI2015]弦论——题解

    https://www.lydsy.com/JudgeOnline/problem.php?id=3998 https://www.luogu.org/problemnew/show/P3975 对于 ...

  8. UOJ #395 BZOJ 5417 Luogu P4770 [NOI2018]你的名字 (后缀自动机、线段树合并)

    NOI2019考前做NOI2018题.. 题目链接: (bzoj) https://www.lydsy.com/JudgeOnline/problem.php?id=5417 (luogu) http ...

  9. 洛谷 P4248 / loj 2377 [AHOI2013] 差异 题解【后缀自动机】【树形DP】

    可能是一个 SAM 常用技巧?感觉 SAM 的基础题好多啊.. 题目描述 给定一个长度为 \(n\) 的字符串 \(S\) ,令 \(T_i\) 表示它从第 \(i\) 个字符开始的后缀,求: \[ ...

  10. 并不对劲的bzoj3998:loj2102:p3975:[TJOI2015]弦论

    题目大意 对于一个给定的长度为n(\(n\leq5*10^5\))的字符串, 分别求出不同位置的相同子串算作一个.不同位置的相同子串算作多个时,它的第k(\(k\leq10^9\))小子串是什么 题解 ...

随机推荐

  1. MySQL同步ES方案

    1. 前言 在实际项目开发中,我们经常将 MySQL 作为业务数据库,ES 作为查询数据库,用来实现读写分离,缓解 MySQL 数据库的查询压力,应对海量数据的复杂查询. 这其中有一个很重要的问题,就 ...

  2. B+树原理详解

    B树 与 B+树 我们今天要介绍的是工作开发中最常接触到的 InnoDB 存储引擎中的 B+ 树索引.要介绍 B+ 树索引,就不得不提二叉查找树,平衡二叉树和 B 树这三种数据结构.B+ 树就是从他们 ...

  3. TOML 1.0格式语法

    github: https://github.com/BurntSushi/toml TOML 旨在成为一个语义显著而易于阅读的最低限度的配置文件格式.TOML 被设计地能够无歧义地转化为哈希表.TO ...

  4. kafka之介绍

    Kafka 由于高吞吐量.可持久化.分布式.支持流数据处理等特性而被广泛应用.但当前关于Kafka原理及应用的相关资料较少,在我打算编写本文时,还没有见到中文版本的Kafka相关书籍,对于初学者甚至是 ...

  5. Blazor 组件库 BootstrapBlazor 中Card组件介绍

    一个较为完整的Card样子 Card组件介绍 Card组件分为三部分,CardHeader.CardBody.CardFooter. 代码格式如下: <Card> <CardHead ...

  6. 【Kotlin】select简介

    1 前言 ​ 协程的 select 是一种用于异步操作的选择器,它允许同时等待多个挂起函数的结果,并在其中一个完成时执行相应的操作. ​ 能够被 select 的事件都是 SelectClause,在 ...

  7. Qt数据库应用10-通用数据库设置

    一.前言 做过了众多的项目,只要是用了数据库的项目,基本上还有一个需求就是,提供一个界面用来初始化数据库,相当于恢复出厂设置的数据,一般恢复出厂设置需要复位的是配置文件.数据库文件.布局文件这几种,其 ...

  8. sql建表添加注释的语句

    官方自带的,我觉得麻烦,稍微'封装'一下 create proc Proc_AddExplian @table varchar(200), @clolum varchar(200), @text va ...

  9. JVM实战—3.JVM垃圾回收的算法和全流程

    大纲 1.JVM内存中的对象何时会被垃圾回收 2.JVM中的垃圾回收算法及各算法的优劣 3.新生代和老年代的垃圾回收算法 4.避免本应进入S区的对象直接升入老年代 5.Stop the World问题 ...

  10. 回顾 2024 年 19 期C#/.NET/.NET Core技术前沿周刊

    前言 转眼之间2024年就只剩最后一天了,咱们的C#/.NET/.NET Core技术前沿周刊也从8月份的第 1 期持续更新到现在已经更新到了第 19 期了,今天咱们一起来回顾一下 2024 年 19 ...