Luogu P5479 [BJOI2015] 隐身术 题解 [ 紫 ] [ 多维 DP ] [ 交换维度 ] [ 后缀数组 ] [ 哈希 ]

news/2025/10/24 21:44:39/文章来源:https://www.cnblogs.com/zhr0102/p/19164292

隐身术:挺巧妙的一道题。

首先考虑一个暴力:将“子串”的条件转化为对每一个后缀的前缀考虑,枚举每一个后缀。然后对每一个后缀做一个编辑距离的 DP,统计答案即可。

具体地,编辑距离的 DP 状态定义为:\(dp_{i, j}\) 表示 \(S_{1\sim i}, T_{1\sim j}\) 之间的最小步数。转移为:

  • \(S_{i+1} = T_{j+1}\),即后一个字符相等时,直接转移:\(dp_{i+1, j + 1}\overset{\min}\leftarrow dp_{i, j}\)
  • \(T\) 执行删除操作 / 对 \(S\) 执行插入操作:此时编辑距离加 \(1\)\(T\) 的匹配长度增加了 \(1\),因此:\(dp_{i, j + 1}\overset{\min}\leftarrow dp_{i, j} + 1\)
  • \(T\) 执行插入操作 / 对 \(S\) 执行删除操作:此时编辑距离加 \(1\)\(S\) 的匹配长度增加了 \(1\),因此:\(dp_{i +1, j }\overset{\min}\leftarrow dp_{i, j} + 1\)
  • \(T\) 执行插入操作 / 对 \(S\) 执行插入操作:此时编辑距离加 \(1\)\(S, T\) 的匹配长度增加了 \(1\),因此:\(dp_{i +1, j + 1}\overset{\min}\leftarrow dp_{i, j} + 1\)

考虑正解。注意到 \(\bm K\) 的值很小,容易想到 DP 优化中一个常见的套路:交换 DP 定义域与值域。并且要求该定义域关于原值域具有单调性

原 DP 的任何一个维度显然关于编辑距离没有单调性。因此可以先找出编辑距离的一个单调性,然后再根据这个单调性找到对应的定义域,重新设计一个 DP。

我们发现,当两个串 \(A, B\) 确定时,如果把 \(A, B\) 的某个相同长度的后缀删除,那么编辑距离一定不会增加。因此,当两个串 \(\bm{A, B}\) 的长度之差确定时,\(\bm{A}\) 的长度和 \(\bm{B}\) 的长度关于编辑距离单调不降

由此可以得到一个优化后的 DP:\(dp_{i, j}\) 表示编辑距离为 \(i\)\(\bm{|T| - |S| = j}\) 时最长 \(\bm S\) 能延伸的长度。转移如下:

  • \(S_{dp_{i, j}+1} = T_{dp_{i, j} + j +1}\),即后若干个字符相等时,直接让 DP 值自增,直到后一个字符不相等。即\(\bm{S, T}\) 某两个后缀的 LCP,可以通过后缀数组 / 哈希二分实现。
  • \(T\) 执行删除操作 / 对 \(S\) 执行插入操作:此时编辑距离加 \(1\)\(T\) 的延伸长度增加了 \(1\)\(|T| - |S|\) 增加了 \(1\)\(S\) 延伸的长度不增加,因此:\(dp_{i +1, j + 1}\overset{\max}\leftarrow dp_{i, j}\)
  • \(T\) 执行插入操作 / 对 \(S\) 执行删除操作:此时编辑距离加 \(1\)\(S\) 的延伸长度增加了 \(1\)\(|T| - |S|\) 减少了 \(1\),因此:\(dp_{i +1, j - 1}\overset{\max}\leftarrow dp_{i, j} + 1\)
  • \(T\) 执行替换操作 / 对 \(S\) 执行替换操作:此时编辑距离加 \(1\)\(S, T\) 的延伸长度增加了 \(1\)\(|T| - |S|\) 不变,因此:\(dp_{i +1, j}\overset{\max}\leftarrow dp_{i, j} + 1\)

就此 DP,如果使用 SA 求 LCP 那么时间复杂度就是 \(O(nk^2)\)。但如果使用哈希二分求 LCP 那么时间复杂度就是 \(O(nk^2\log n)\)

哈希二分 Version:

#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi = pair<int, int>;
const int N = 100005, K = 10;
const ll mod = 998244853, base = 13163;
mt19937_64 rnd((unsigned) time(NULL));
ll rd(ll l, ll r)
{return uniform_int_distribution<ll> (l, r) (rnd);
}
int n, m, p;
char s[N], t[N];
ll hs1[N], hs2[N], pw[N], cw[N], ans;
void do_hash()
{pw[0] = 1;for(int i = 1; i < N; i++) pw[i] = (pw[i - 1] * base) % mod;for(int i = 0; i < 30; i++) cw[i] = rd(1, base - 1);for(int i = 1; i <= n; i++)hs1[i] = (hs1[i - 1] * base % mod + cw[s[i] - 'A']) % mod;for(int i = 1; i <= m; i++)hs2[i] = (hs2[i - 1] * base % mod + cw[t[i] - 'A']) % mod;
}
ll geths1(int l, int r)
{return ((hs1[r] - (hs1[l - 1] * pw[r - l + 1] % mod)) % mod + mod) % mod;
}
ll geths2(int l, int r)
{return ((hs2[r] - (hs2[l - 1] * pw[r - l + 1] % mod)) % mod + mod) % mod;
}
ll LCP(int x, int y)
{int l = 1, r = min(n - x + 1, m - y + 1);while(l < r){int mid = (l + r + 1) >> 1;if(geths1(x, x + mid - 1) == geths2(y, y + mid - 1)) l = mid;else r = mid - 1;}if(geths1(x, x + l - 1) != geths2(y, y + l - 1)) return 0;return l;
}
int dp[K][2 * K];
void solve(int sx)
{memset(dp, -0x3f, sizeof(dp));dp[0][p] = 0;int mxv = 0;set<int> s;for(int k = 0; k <= p; k++){for(int j = 0; j <= 2 * p; j++){if(dp[k][j] < 0) continue;dp[k][j] = min(dp[k][j], min(n, m - sx - j + p + 1));int lcp = LCP(1 + dp[k][j], sx + dp[k][j] + j - p);dp[k][j] = (dp[k][j] + lcp);if(dp[k][j] == n){if(sx + n - 1 + j - p >= sx && sx + n - 1 + j - p <= m)s.insert(sx + n - 1 + j - p);}mxv = max(mxv, dp[k][j]);if(k == p) continue;dp[k + 1][j] = max(dp[k + 1][j], dp[k][j] + 1);if(j + 1 <= 2 * p) dp[k + 1][j + 1] = max(dp[k + 1][j + 1], dp[k][j]);if(j - 1 >= 0) dp[k + 1][j - 1] = max(dp[k + 1][j - 1], dp[k][j] + 1);}}ans += s.size();
}
int main()
{//freopen("sample.in", "r", stdin);//freopen("sample.out", "w", stdout);ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin >> p;cin >> s + 1 >> t + 1;n = strlen(s + 1);m = strlen(t + 1);do_hash();for(int i = 1; i <= m; i++)solve(i);cout << ans;return 0;
}

后缀数组 Version:

#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi = pair<int, int>;
const int N = 400005, K = 10;
int n, m, p;
int len, cm, x[N], y[N], c[N], sa[N];
ll ans;
char s[N], t[N];
void getsa()
{cm = 127;int i, k;for(i = 1; i <= len; i++) c[x[i] = s[i]]++;for(i = 1; i <= cm; i++) c[i] += c[i - 1];for(i = len; i >= 1; i--) sa[c[x[i]]--] = i;for(k = 1; k <= len; k <<= 1){memset(c, 0, sizeof(c));for(i = 1; i <= len; i++) y[i] = sa[i];for(i = 1; i <= len; i++) c[x[y[i] + k]]++;for(i = 1; i <= cm; i++) c[i] += c[i - 1];for(i = len; i >= 1; i--) sa[c[x[y[i] + k]]--] = y[i];memset(c, 0, sizeof(c));for(i = 1; i <= len; i++) y[i] = sa[i];for(i = 1; i <= len; i++) c[x[y[i]]]++;for(i = 1; i <= cm; i++) c[i] += c[i - 1];for(i = len; i >= 1; i--) sa[c[x[y[i]]]--] = y[i];for(i = 1; i <= len; i++) y[i] = x[i];for(cm = 0, i = 1; i <= len; i++){if(y[sa[i]] == y[sa[i - 1]] && y[sa[i] + k] == y[sa[i - 1] + k]) x[sa[i]] = cm;else x[sa[i]] = ++cm;}if(cm == len) return;}
}
int rk[N], ht[N];
void getht()
{for(int i = 1; i <= len; i++) rk[sa[i]] = i;for(int i = 1, k = 0; i <= len; i++){if(k) k--;int j = sa[rk[i] - 1];while(i + k <= len && j + k <= len && s[i + k] == s[j + k]) k++;ht[rk[i]] = k;}
}
int st[20][N], lg2[N];
void init()
{for(int i = 1; i <= len; i++) st[0][i] = ht[i];lg2[1] = 0;for(int i = 2; i < N; i++) lg2[i] = (lg2[i >> 1] + 1);for(int j = 1; j < 20; j++)for(int i = 1; i + (1 << j) - 1 <= len; i++)st[j][i] = min(st[j - 1][i], st[j - 1][i + (1 << (j - 1))]);
}
int LCP(int x, int y)
{y += n + 1;x = rk[x]; y = rk[y];if(x > y) swap(x, y);x++;int lenx = y - x + 1;int lg = lg2[lenx];return min(st[lg][x], st[lg][y - (1 << lg) + 1]);
}
int dp[K][2 * K], cnt, dmdf[N];
bitset<N> vis;
void solve(int sx)
{memset(dp, -0x3f, sizeof(dp));dp[0][p] = 0;int mxv = 0;cnt = 0;for(int k = 0; k <= p; k++){for(int j = 0; j <= 2 * p; j++){if(dp[k][j] < 0) continue;dp[k][j] = min(dp[k][j], min(n, m - sx - j + p + 1));int lcp = LCP(1 + dp[k][j], sx + dp[k][j] + j - p);dp[k][j] = (dp[k][j] + lcp);if(dp[k][j] == n){if(sx + n - 1 + j - p >= sx && sx + n - 1 + j - p <= m){if(vis[sx + n - 1 + j - p] == 0){vis[sx + n - 1 + j - p] = 1;dmdf[++cnt] = sx + n - 1 + j - p;ans++;}}}mxv = max(mxv, dp[k][j]);if(k == p) continue;dp[k + 1][j] = max(dp[k + 1][j], dp[k][j] + 1);if(j + 1 <= 2 * p) dp[k + 1][j + 1] = max(dp[k + 1][j + 1], dp[k][j]);if(j - 1 >= 0) dp[k + 1][j - 1] = max(dp[k + 1][j - 1], dp[k][j] + 1);}}for(int i = 1; i <= cnt; i++) vis[dmdf[i]] = 0;
}
int main()
{//freopen("sample.in", "r", stdin);//freopen("sample.out", "w", stdout);ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin >> p;cin >> s + 1 >> t + 1;n = strlen(s + 1);m = strlen(t + 1);len = n + m + 1;s[n + 1] = '#';for(int i = n + 2, j = 1; i <= len; i++, j++) s[i] = t[j];getsa();getht();init();for(int i = 1; i <= m; i++)solve(i);cout << ans;return 0;
}

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

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

相关文章

2025年10月23日

A. 萌萌甜酱 洛谷原题:https://www.luogu.com.cn/problem/P3621题目 你准备给弟弟 Ike 买一件礼物,但是,Ike 挑选礼物的方式很特别:他只喜欢那些能被他排成有序形状的东西。 你准备给 Ike 买一个风铃。风铃是一种多…

大象《Thinking in Projects》读书笔记2

大象《Thinking in Projects》第二部分聚焦项目执行阶段的核心逻辑与实践方法,与第一部分强调的项目思维搭建形成紧密衔接,为大三学生从理论学习转向实践落地提供了关键指引。​ 这部分内容首先深入剖析了项目进度管…

06_DNS解析:从域名到IP地址

本文将通过C语言,手写实现一个基于UDP编程的DNS域名解析。一.DNS是什么? DNS(Domain Name System)的作用将我们容易记忆的域名转换为计算机可以处理的IP地址,DNS 使用 TCP 和 UDP 端口 53。 常用的nslookup命令,可…

ABP - 接口授权 [Authorize、AllowAnonymous、IPermissionChecker]

接口授权(Authorization) 核心辅助类:[Authorize]:标记需要授权的接口。 [AllowAnonymous]:允许匿名访问。 IPermissionChecker:手动检查权限。接口授权(Authorization)核心类示例与讲解 接口授权是在“身份认…

日总结 17

对比维度 BS 架构(Browser/Server,浏览器 / 服务器) CS 架构(Client/Server,客户端 / 服务器) 部署方式 仅需部署服务器,用户通过浏览器(如 Chrome、Edge)访问,无需安装客户端。 需同时部署服务器和客户端,…

杂题选做-3

杂题选做-3 #21 P9755 题目传送门 首先,看上去这个题直接不是很友好,我们考虑二分转为判断性问题。 然后一个这类在树上 topo 遍历每一个节点的最优方案类问题有一个 Trick:我们找出当前最优的节点,将其与其父亲合…

10.24每日总结

今天主要的课程有人机交互和机器学习,软考网课已经学到第六章了,前两天没有上传笔记,一会儿把这两三天的都传到这篇,距离考试还有半个多月,加油!

利用Eval Villain挖掘CSPT漏洞的完整指南

本文详细介绍了如何使用Eval Villain工具来发现和利用客户端路径遍历(CSPT)漏洞。通过Doyensec的CSPT演练环境,逐步演示了从漏洞发现到利用的完整过程,包括工具配置、漏洞检测、响应分析和最终利用链构建。CSPT the …

Button按钮插入图片后仍有白色边框的解决办法

别人的大道再好,那也是别人的道路,不妨埋头做事,但问耕耘莫问收获,偶尔抬头,左右看两眼其它路上的人物风光,就够了。起因:我设置完背景图片后 ,边框仍有白色边框 ,看起来很别扭 如图所示:解决方法: 将FlatA…

Unity静态资源优化

Unity静态资源优化 目录1.音效资源Force To MonoNormalizeLoad In BackgroundAmbisonicLoad In Background / Preload Audio DataCompression FormatLoad TypeSample Rate Setting音频配置2.Model导入设置检查与优化定…

【Java】Spring @Transactional 事务失效:9大经典『陷阱』及终极解决方案

@Transactional 注解是 Spring 框架中声明式事务管理的核心,它极大地简化了开发人员的事务管理工作。然而,在日常开发中,我们常常会遇到一个令人困惑的问题:“明明加了 @Transactional 注解,为什么事务没有生效?…

【模板】动态 dp 学习笔记(树剖版)

动态 dp 学习笔记(树剖版) 本文同步发表于 cnblogs。 本文同步发表于 luogu。 前置知识:简单 dp 树链剖分 矩阵乘法和广义矩阵乘法P4719 【模板】动态 DP 本文着重讲下修改的具体过程以及代码实现,蒟蒻花了好长时间…

ABP - JWT 鉴权(JWT Authentication)[AbpJwtBearerModule、JwtBearerOptions]

JWT 鉴权(JWT Authentication) 核心辅助类:AbpJwtBearerModule:JWT集成模块。 JwtBearerOptions:JWT配置选项。JWT(JSON Web Token)是ABP框架中常用的无状态鉴权方案,核心作用是“让客户端携带Token访问接口,…

软考四

软考四Posted on 2025-10-24 21:33 心默默言 阅读(0) 评论(0) 收藏 举报1. 操作系统的概念2. 进程管理3. 存储管理4. 文件管理

CSP-S2022

T1 首先可以花 \(O(n(n + m))\) 的时间求出任意两个点是否可以通过至多转车 \(k\) 次到达。若 \(f_{u, v} = 1\) 表示可以,否则不可以。 先暂时忽略景点不能相同的限制。因为有 \(4\) 个景点,不能 \(O(n^4)\) 全部枚…

Hugo主题定制速查手册

以下是为你整理的《Hugo主题定制速查手册》,涵盖核心配置、模板修改、样式调整、功能扩展等关键场景,方便快速查阅: 一、核心配置速查表(config.toml)配置类别 关键参数示例(TOML格式) 说明网站基础信息 baseUR…

10/24

今天没课

Hugo主题的修改和配置

Hugo主题的修改和配置核心围绕配置文件(控制全局参数)和主题目录结构(修改页面布局、样式与功能)展开,本质是通过调整参数和自定义模板来实现个性化需求。 一、主题配置:通过配置文件控制全局参数 Hugo的配置文件…

多元生成函数+多项式方程组——[AGC058D] Yet Another ABC String

多元生成函数+多项式方程组——[AGC058D] Yet Another ABC String 计算小练习 7(AGC085D) - 洛谷专栏

最小生成树 kruskal算法

一个贪心算法,先排序,然后从小到大开始选边; 同时用并查集来维护两个点是否连通,如果当前边连接的两个点已经连通,那么说明选这条边没有任何意义,一定是劣的(因为前面已经排了序) #include<bits/stdc++.h&g…