CF611D New Year and Ancient Prophecy

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 1n5000。求所有可能的方案数,答案对 1 0 9 + 7 10^9+7 109+7取模

思路

以下 lst、rst 代表 l_start、r_startled、red 代表l_end、r_end

读完题一眼可以看出的是: d p dp dp 数组显然是 f[i][j] 代表以 i i i 为结尾,长度为 j j j 的合法方案数。由 d p dp dp 数组也能很快得到状态转移方程为

  • 对于所有Llen<Rlenstr[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]​

这样除了枚举 rstRlen 以外,还需要枚举 Llen,加上字符串比较 O ( n ) O(n) O(n) ,总复杂度是 O ( n 4 ) O(n^4) O(n4)


我们考虑前缀和优化,将 f[i][j] 的含义改为以 i i i 为结尾,长度小于等于 j j j 的合法方案数。这样,遍历redlen时,对于固定的 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)挺显然的做法硬是没调出来。这次翻车幸好有惊无险,也算个警示吧,有空还是得复健复健

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值