CF611D New Year and Ancient Prophecy
赛时写个 O ( n 3 ) O(n^3) O(n3)的都被初始化和边界卡红温了,没ak太耻辱了,仅以本文鞭策自己
题意
给一个 n n n位数,要求将该 n n n位数拆分成若干数字,且需满足:
- 数字的顺序要严格递增
- 数字都是正整数
- 没有前导零
其中 1 ≤ n ≤ 5000 1\le n\le 5000 1≤n≤5000。求所有可能的方案数,答案对 1 0 9 + 7 10^9+7 109+7取模
思路
以下 lst、rst
代表 l_start、r_start
,led、red
代表l_end、r_end
读完题一眼可以看出的是:
d
p
dp
dp 数组显然是 f[i][j]
代表以
i
i
i 为结尾,长度为
j
j
j 的合法方案数。由
d
p
dp
dp 数组也能很快得到状态转移方程为
- 对于所有
Llen<Rlen
且str[rst]!='0'
,f[red][Rlen]+=f[led][Llen]
- 对于
Llen==Rlen
且str[lst...led]<str[rst...ted]
且str[rst]!='0'
,f[red][Rlen]+=f[led][Llen]
这样除了枚举 rst
和 Rlen
以外,还需要枚举 Llen
,加上字符串比较
O
(
n
)
O(n)
O(n) ,总复杂度是
O
(
n
4
)
O(n^4)
O(n4)的
我们考虑前缀和优化,将 f[i][j]
的含义改为以
i
i
i 为结尾,长度小于等于
j
j
j 的合法方案数。这样,遍历red
和len
时,对于固定的 len
- 若
str[lst...led]<str[rst...red]
我们可以通过f[red][len]+=f[led][len]
转移 - 否则我们可以通过
f[red][len]+=f[led][len-1]
转移(相同长度的串无法转移,而短一点就可以全部转移)
注意初始化为f[0][0]=1
以及边界问题。赛时我也已经忘了怎么想的反正在这卡了很久,怎么改怎么错,太菜了
这样我们省去了枚举 L l e n Llen Llen的过程,总复杂度是 O ( n 3 ) O(n^3) O(n3)。至此,减法取模在cf神机上已经能卡过去了。
#include <bits/stdc++.h>
const int mod = 1e9 + 7;
void solve()
{
int n;
std::string str;
std::cin >> n >> str;
str = " " + str;
std::vector<std::vector<int>> f(n + 1, std::vector<int>(n + 1, 0));
f[0][0] = 1;
auto ck = [](int &x) -> void
{ if (x >= mod)x -= mod; };
for (int red = 1; red <= n; red++)
{
for (int len = 1; len <= red; len++)
{
int led = red - len;
int lst = led - len + 1;
int rst = led + 1;
if (str[rst] == '0')
continue;
if (lst <= 0)
f[red][len] += f[led][led];
else
{
std::string tmpl = str.substr(lst, len);
std::string tmpr = str.substr(rst, len);
if (tmpl < tmpr)
f[red][len] += f[led][len];
else
f[red][len] += f[led][len - 1];
}
ck(f[red][len]);
}
for (int len = 1; len <= red; len++)
{
f[red][len] += f[red][len - 1];
ck(f[red][len]);
}
}
std::cout << f[n][n] << std::endl;
}
signed main()
{
std::ios::sync_with_stdio(false), std::cin.tie(nullptr);
int T;
// cin >> T;
T = 1;
while (T--)
solve();
}
正解还要省去字符串比较的
O
(
n
)
O(n)
O(n)达到
O
(
n
2
)
O(n^2)
O(n2)复杂度。等长的字符串比较是逐位比较直至不相等的一位,因此我们考虑预处理原字符串的LCP,使得等长字符串比较直接加速比较到不匹配的一位。LCP可以倒着扫描字符串暴力
O
(
n
2
)
O(n^2)
O(n2)预处理,lcp[i][j]
代表第
i
i
i位与第
j
j
j位匹配时的最长公共子串长度。注意使用LCP加速匹配之后判断一下是否越界
#include <bits/stdc++.h>
const int mod = 1e9 + 7;
void solve()
{
int n;
std::string str;
std::cin >> n >> str;
str = " " + str;
std::vector<std::vector<int>> lcp(n + 2, std::vector<int>(n + 2, 0));
for (int i = n; i >= 1; i--)
for (int j = n; j >= 1; j--)
if (str[i] == str[j])
lcp[i][j] = lcp[i + 1][j + 1] + 1;
std::vector<std::vector<int>> f(n + 1, std::vector<int>(n + 1, 0));
f[0][0] = 1;
auto ck = [](int &x) -> void
{ if (x >= mod)x -= mod; };
for (int red = 1; red <= n; red++)
{
for (int len = 1; len <= red; len++)
{
int led = red - len;
int lst = led - len + 1;
int rst = led + 1;
if (str[rst] == '0')
continue;
if (lst <= 0)
f[red][len] += f[led][led];
else
{
int maxn = std::min(lcp[lst][rst], len - 1);
if (rst + maxn <= n && str[lst + maxn] < str[rst + maxn])
f[red][len] += f[led][len];
else
f[red][len] += f[led][len - 1];
}
ck(f[red][len]);
}
for (int len = 1; len <= red; len++)
{
f[red][len] += f[red][len - 1];
ck(f[red][len]);
}
}
std::cout << f[n][n] << std::endl;
}
signed main()
{
std::ios::sync_with_stdio(false), std::cin.tie(nullptr);
int T;
// cin >> T;
T = 1;
while (T--)
solve();
}
太久没训码力确实下降了很多, O ( n 3 ) O(n^3) O(n3)挺显然的做法硬是没调出来。这次翻车幸好有惊无险,也算个警示吧,有空还是得复健复健