cf611 D. New Year and Ancient Prophecy(dp+LCP)

题意:

把一个n位整数切成若干段,得到若干个整数。要求每个数都不为0,每个数都没有前缀0,且前一个数严格小于后一个数。问切割数方案取模。

\(n\le 5000\)

思路:

\(O(n^2)\) 的dp,\(f(l,r)\) 表示最后一段是 \([l,r]\) 的方案数,则答案是 \(\sum\limits _i f(i,n)\)

状态转移:若(作为一个整数的)\(a[l,i]<a[i+1,r]\) ,则 \(f(i+1,r)+=f(l,i)\)

双指针,\(l\) 从 \(i\) 开始往左走,\(r\) 从 \(i+1\) 开始走到 \(a[l,i]<a[i+1,r]\) 的第一个位置,那么 \(f(i+1,r\sim n)\) 都能被更新,也就是一个后缀被更新。开个差分数组处理区间加。

注意不能有前导零,且要初始化 \(f(1,1\sim n)=1\)

上面的dp不难想到,但是一般比较两个子串是 \(O(n)\) 的,需要加速。一个方法是 $ O(n^2)$ 预处理 \(lcp(i,j)\) 表示以 \(a_i,a_j\) 开始的最长公共子串的长度。

const int N = 5005, MOD = 1e9 + 7;
int n, lcp[N][N], f[N][N], b[N];
char a[N];
void init()
{
    for(int i = n; i; i--)
        for(int j = n; j; j--)
            if(a[i] == a[j])
                lcp[i][j] = lcp[i+1][j+1] + 1;
}
bool ok(int l1, int r1, int l2, int r2)
{ //前一个是不是比后一个小
    if(r1-l1 != r2-l2) return r1-l1 < r2-l2;
    int p = lcp[l1][l2];
    if(p >= r1 - l1 + 1) return 0;
    return a[l1+p] < a[l2+p];
}
signed main()
{
    cin >> n >> a + 1;
    init();

    for(int i = 1;  i <= n; i++) f[1][i] = 1;
    for(int i = 1; i < n; i++)
    {
        if(a[i+1] == '0') continue; //不能有前导零
        memset(b, 0, sizeof b);
        int r = i + 1;
        for(int l = i; l; l--)
        {
            while(r <= n && !ok(l,i,i+1,r)) r++;
            if(r <= n) (b[r] += f[l][i]) %= MOD;
        }
        for(int j = i + 1; j <= n; j++)
            (b[j] += b[j-1]) %= MOD, (f[i+1][j] += b[j]) %= MOD;
    }

    int ans = 0;
    for(int i = 1; i <= n; i++) (ans += f[i][n]) %= MOD;
    cout << ans;
}

上一篇:BaoStock:使用python的baostock接口,查询季频盈利能力


下一篇:P7811 [JRKSJ R2] 你的名字。 解题报告