Luogu P3975 TJOI2015 弦论 题解 [ 紫 ] [ 后缀自动机 ] [ 动态规划 ] [ 拓扑排序 ]

news/2025/1/15 0:44:42/文章来源:https://www.cnblogs.com/zhr0102/p/18671969

弦论:本来不想写板子题题解的,但奈何这道题的题解都太垃圾了,导致我理解了一个晚上都没想明白 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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/869328.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Easysearch Rollup 使用指南

背景 在现代数据驱动的世界中,时序数据的处理变得越来越重要。无论是监控系统、日志分析,还是物联网设备的数据收集,时序数据都占据了大量的存储空间。随着时间的推移,这些数据的存储成本和管理复杂度也在不断增加。 为了解决这一问题,Rollup 技术应运而生。本文将带你深入…

.NET 数据拷贝方案选择

应用中我们经常使用到数据的复制,在.NET中有多种方式可以实现复制数据或对象。选择哪种方式通、是浅拷贝还是深拷贝,取决于对象的复杂性、数据量以及具体需求场景。1. MemberwiseClone拷贝 浅拷贝 Object.MemberwiseClone 方法 (System) | Microsoft Learn,指针对对象执行非…

1.14 eclipse配置spring

今天完成了eclipse配置springboot eclipse本身并没有spring项目,需要在eclipse市场下载插件选择tool4安装安装完成等待eclipse加载,全部安装完成后即可创建spring项目

英语语法(标点符号:逗号和撇号)

结束句子的三种方法 认识逗号

深入浅出:Agent如何调用工具——从OpenAI Function Call到CrewAI框架

深入浅出:Agent如何调用工具——从OpenAI Function Call到CrewAI框架 嗨,大家好!作为一个喜欢折腾AI新技术的算法攻城狮,最近又学习了一些Agent工作流调用工具的文章,学完之后,我真的是“啊这”,一边感慨AI技术的强大,一边觉得自己打开了新世界的大门。于是,我决定写这…

在Ubantu中安装pycharm

1.下载pycharm linux版,我下载的是2022.3.3专业版 2. 更改host文件,输入: sudo gedit /etc/hosts在弹出的文件中的末尾加以下代码: 0.0.0.0 account.jetbrains.com3.激活pycharm: 将pycharm补丁jet-netfilter拷入ubantu中某一路径(注意是整个文件夹放进去,不要只放jar包…

docker-compose自动部署go项目全流程,本地到镜像仓库到服务器,踩坑笔记

声明:个人所学记录,有可以改进的地方希望不吝指教 Dockerfile # 使用golang官方镜像作为构建环境 FROM golang:1.23-alpine AS builder# 设置工作目录 WORKDIR /app# 设置环境变量镜像变量 ENV GO111MODULE=on ENV GOPROXY=https://goproxy.cn,direct# 复制go.mod 和 go.sum文…

docker部署d2l环境

编写dockerfile # 使用NVIDIA提供的CUDA基础镜像,包含CUDA 11.8.0和cuDNN 8,基于Ubuntu 22.04 FROM nvidia/cuda:11.8.0-cudnn8-devel-ubuntu22.04 # 设置维护者信息 MAINTAINER watcherprime <woma@126.com># 设置环境变量,包括时区、非交互式前端和PATH变量 ENV TZ=…

【TCP协议】TCP Keepalive 指南

1、什么是 TCP Keepalive?TCP Keepalive 是一种 TCP 协议内置的探测机制,用于检测长时间未活动的连接是否仍然存活。当启用了 Keepalive 后,TCP 会在连接空闲一定时间后,定期向对端发送探测包,如果未收到对端的响应,则会尝试多次探测,最终关闭连接。 用途: 检测并清理死…

《CPython Internals》阅读笔记:p151-p151

《CPython Internals》学习第 9天,p151-p1510 总结,总计 1 页。 一、技术总结 无。 二、英语总结(生词:1) 1.marshal (1)marshaling Marshalling or marshaling(US spelling) is the process of transforming the memory representation of an object into a data form su…

# vm逆向

vm逆向 虚拟机逆向与实现-CSDN博客 对上面博客的总结。 引 vm逆向题,一般是小型虚拟机程序,可以理解为一种模拟器,有start,dispatcher,opcode等结构。常见使用while-switch/if这类循环+选择结构来实现简单的虚拟机模拟,如下:逆向重点:分析入口,搞清输入和opcode的位置理…

【Gossip 协议】Redis 集群中节点之间的通信方式?

# 分布式系统 # Gossip 协议 在分布式系统中,不同的节点进行数据/信息共享是一个基本的需求。 一种比较简单粗暴的方法就是 集中式发散消息,简单来说就是一个主节点同时共享最新信息给其他所有节点,比较适合中心化系统。这种方法的缺陷也很明显,节点多的时候不光同步消息的…