「2017 山东一轮集训 Day6」子序列
复盘
这可以来一手反复 AC 鞭尸(
Description#
区间本质不同子序列,母串长度
Analysis#
本来还有一个区间本质不同子串,但显然这俩都不是一个类型的(
显然子序列和子串都不是一个量级的,肯定又要用到压缩状态的好手:
DP。
我们可以考虑只枚举最后一位是什么,我们可以考虑在前面
所以对于结尾不是
而且这不会算重:
假如存在形如 “STS” 的形式,当后面的 “S” 完成了拼接的之后,前面的 “S” 可以视作拼接成了 “S__S” 的样子,并没有重,反而正好不漏。
DP 转移如下:
最终答案就是:
那我们就得到了
然后你就有
sol0
/*
*/
#include
using namespace std;
#define File(a) freopen(a".in", "r", stdin), freopen(a".out", "w", stdout);
#define Check(a) freopen(a".in", "r", stdin), freopen(a".ans", "w", stdout);
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair pii;
#define fi first
#define se second
#define mp std::make_pair
const int mod = 1e9 + 7;
template
inline int M(A x) {return x;}
template
inline int M(A x, B ... args) {return 1ll * x * M(args...) % mod;}
#define mi(x) (x >= mod) && (x -= mod)
#define ad(x) (x < 0) && (x += mod)
const int N = 1e5 + 10;
int n, q, f[N][10], su[N][10];
char s[N];
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cin >> (s + 1) >> q; n = strlen(s + 1);
for (int i = 1, l, r; i <= q; ++i) {
std::cin >> l >> r;
for (int k = 0; k < 9; ++k) {
f[l - 1][k] = su[l - 1][k] = 0;
}
f[l - 1][9] = su[l - 1][9] = 1;
for (int j = l; j <= r; ++j) {
for (int k = 0; k < 10; ++k) {
if (k == s[j] - 'a') {
f[j][k] = su[j - 1][9];
}
else f[j][k] = f[j - 1][k];
if (!k) su[j][k] = f[j][k];
else su[j][k] = su[j][k - 1] + f[j][k];
mi(su[j][k]);
}
}
std::cout << su[r][9] - 1 << "\n";
}
return 0;
}
Sol1#
发现转移比较单一,考虑能不能矩阵预处理一下。
因为不等于是对应位直接转移,等于至整体转移,所以就是对角线全是 1 以及其中一行全是 1 :
这样的话答案就会形如一段
那无论是预处理还是查询就都是
因为常熟比较小,然后你就可以过了,不过我们并不满足(
Sol1
/*
*/
#include
using namespace std;
#define File(a) freopen(a".in", "r", stdin), freopen(a".out", "w", stdout);
#define Check(a) freopen(a".in", "r", stdin), freopen(a".ans", "w", stdout);
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair pii;
#define fi first
#define se second
#define mp std::make_pair
const int mod = 1e9 + 7;
template
inline int M(A x) {return x;}
template
inline int M(A x, B ... args) {return 1ll * x * M(args...) % mod;}
#define mi(x) (x >= mod) && (x -= mod)
#define ad(x) (x < 0) && (x += mod)
const int N = 1e5 + 10;
int n, q;
char s[N];
struct matrix {
int a[10][10];
matrix() {memset(a, 0, sizeof(a));}
inline void init() {for (int i = 0; i < 10; ++i) a[i][i] = 1;}
matrix operator * (const matrix &it) const {
matrix t;
for (int i = 0; i < 10; ++i) {
for (int k = 0; k < 10; ++k) {
if (!a[i][k]) continue;
for (int j = 0; j < 10; ++j) {
t.a[i][j] += M(a[i][k], it.a[k][j]);
mi(t.a[i][j]);
}
}
}
return t;
}
} pre[N], suf[N];
inline void init() {
pre[0].init(); suf[0].init();
for (int i = 1, c; i <= n; ++i) {
c = s[i] - 'a';
for (int j = 0; j < 10; ++j) {
suf[i].a[c][j] = 1;
pre[i].a[c][j] = mod - 1;
}
pre[i].init(); suf[i].init();
}
for (int i = 2; i <= n; ++i) {
pre[i] = pre[i - 1] * pre[i];
suf[i] = suf[i] * suf[i - 1];
}
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cin >> (s + 1) >> q; n = strlen(s + 1);
init();
for (int i = 1, l, r; i <= q; ++i) {
std::cin >> l >> r;
matrix res = suf[r] * pre[l - 1];
int ans = 0;
for (int j = 0; j < 10; ++j) {
ans += res.a[j][9]; mi(ans);
}
std::cout << ans - 1 << "\n";
}
return 0;
}
Sol2#
很明显这个矩阵只不过是在单位矩阵的基础上加了一点点东西。
然后手玩一下,发现对于前缀积,每次乘完之后仅会有一行会整体变化,并且正好是当前字符所在的位置。
其实想想就能明白了(
所以预处理可以直接抓着那一行更新,
然后考虑到最后我们能用到的答案进在一列上,所以我们最后计算也只算那一列的,
然后就能快乐的得到
现在你可以几乎可以在
Sol2
/*
*/
#include
using namespace std;
#define File(a) freopen(a".in", "r", stdin), freopen(a".out", "w", stdout);
#define Check(a) freopen(a".in", "r", stdin), freopen(a".ans", "w", stdout);
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair pii;
#define fi first
#define se second
#define mp std::make_pair
const int mod = 1e9 + 7;
template
inline int M(A x) {return x;}
template
inline int M(A x, B ... args) {return 1ll * x * M(args...) % mod;}
#define mi(x) (x >= mod) && (x -= mod)
#define ad(x) (x < 0) && (x += mod)
const int N = 1e5 + 10;
int n, q;
char s[N];
struct matrix {
int a[10][10];
matrix() {memset(a, 0, sizeof(a));}
inline void init() {for (int i = 0; i < 10; ++i) a[i][i] = 1;}
} pre[N], suf[N];
inline void init() {
pre[0].init(); suf[0].init();
for (int i = 0, c; i < 10; ++i) {
c = s[1] - 'a';
pre[1].a[c][i] = 1;
suf[1].a[c][i] = mod - 1;
pre[1].a[i][i] = suf[1].a[i][i] = 1;
}
for (int i = 2, c; i <= n; ++i) {
c = s[i] - 'a';
pre[i] = pre[i - 1];
for (int j = 0; j < 10; ++j) {
pre[i].a[c][j] = 0;
for (int k = 0; k < 10; ++k) {
pre[i].a[c][j] += pre[i - 1].a[k][j];
mi(pre[i].a[c][j]);
}
}
suf[i] = suf[i - 1];
for (int j = 0; j < 10; ++j) {
for (int k = 0; k < 10; ++k) {
if (k == c) continue;
suf[i].a[j][k] -= suf[i - 1].a[j][c];
ad(suf[i].a[j][k]);
}
}
}
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cin >> (s + 1) >> q; n = strlen(s + 1);
init();
for (int i = 1, l, r; i <= q; ++i) {
std::cin >> l >> r;
int ans = 0;
for (int j = 0; j < 10; ++j) {
for (int k = 0; k < 10; ++k) {
ans += M(pre[r].a[j][k], suf[l - 1].a[k][9]);
mi(ans);
}
}
std::cout << ans - 1 << "\n";
}
return 0;
}
Sol3#
感觉优化预处理已经变得比较麻烦了,先来想想查询。
我们每次都要把最后一列用矩阵再还原出来。但其实这个还原很大一部分都只是无聊的枚举,可以直接在预处理解决,
我们前面已经提到了矩阵的变化仅限于其中一行,我们又想对这一行下手了:
乘了一个第一行满的:
再来一次:
乘了一个第三行满的:
我们发现好像整一整行的整体变化就是那一列所有的数加起来,因为那一行全部都是
简单维护一下列的和就行了,
对于后缀积呢?
甚至不用打表,从写的式子上都看得出来,对于每一行,就是:
假如修改位置
然后你就可以对于每一行维护一个
对鎏!!1
那每次递推都是
统一起来就是
虽然实测这个只比 Sol2 快了
Sol3
/*
*/
#include
using namespace std;
#define File(a) freopen(a".in", "r", stdin), freopen(a".out", "w", stdout);
#define Check(a) freopen(a".in", "r", stdin), freopen(a".ans", "w", stdout);
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair pii;
#define fi first
#define se second
#define mp std::make_pair
const int mod = 1e9 + 7;
template
inline int M(A x) {return x;}
template
inline int M(A x, B ... args) {return 1ll * x * M(args...) % mod;}
#define mi(x) (x >= mod) && (x -= mod)
#define ad(x) (x < 0) && (x += mod)
const int N = 1e5 + 10;
int n, q, fp[N][10], fs[N][10], pr[10][10], su[10][10];
char s[N];
inline void init() {
for (int j = 0; j < 10; ++j) {
pr[j][j] = su[j][j] = fp[0][j] = 1;
}
for (int i = 1, ch; i <= n; ++i) {
ch = s[i] - 'a';
for (int j = 0; j < 10; ++j) {
fp[i][j] = (fp[i - 1][j] << 1) - pr[ch][j];
mi(fp[i][j]); ad(fp[i][j]);
pr[ch][j] = fp[i - 1][j];
fs[i][j] = su[ch][j];
su[ch][j] = (su[ch][j] << 1) - fs[i - 1][j];
mi(su[ch][j]); ad(su[ch][j]);
}
}
}
int ans;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cin >> (s + 1) >> q; n = strlen(s + 1);
init();
for (int i = 1, l, r; i <= q; ++i) {
std::cin >> l >> r;
ans = fp[r][9] - 1;
for (int j = 0; j < 9; ++j) {
ans -= M(fp[r][j], fs[l - 1][j]);
ad(ans);
}
std::cout << ans << "\n";
}
return 0;
}
深深明白自己的弱小。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?