16 ACwing 284 金字塔 题解

金字塔

题面

给定一个树的 dfs 序,每个点在被遍历到的时候会被加入到 dfs 序中,也就是说每条边会被遍历两次,两个方向各一次,而这两次会分别加入一个点,所以算上一开始的根,dfs序的长度为 \(2n - 1\) ,用一个字符串 \(S\) 来表示dfs序

\(|S| \le 300\)

题解

首先dfs序表示一个区间,所以我们大概可以想到用一个区间来表示一个子树

\(f(l,r)\) 表示dfs序为 \([l,r]\) 这段区间的树有多少种

考虑如何分解成子问题,对于一个树,它的方案数应该是各个子树的方案数相乘的得数

但是如果我们直接去枚举每个分界线的话,时间复杂度过高,不能接受

如果让 \(f(l,r)\) 表示有一些子树的一个树的方案数,那么可能会算重

这里给出一种,我们可以枚举第一个子树和其他子树的分界,这样保证不会算重,并且只需要枚举一个分界即可

另外,我们要考虑处理一些细节

给个样例:ABABABA

模拟一下将一个树分成第一个子树和其他子树的过程,这个样例我们可以分解成 A B ABABA ,第一个 A 表示第一次遍历根节点,后面表示一些子树的方案数,假设我们枚举后面的起点 \(k\) ,那么必须满足 \(S_l = S_k = S_r\)

并且每个子树的dfs序长度必定是奇数

code

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>

using namespace std;

typedef long long ll;

const int N = 310;
const int mod = 1e9;

int f[N][N];
char s[N];

int main () {
    scanf ("%s", s + 1);
    
    int n = strlen (s + 1);

    if (n % 2 == 0) {
        cout << 0;
        return 0;
    }

    memset (f, 0, sizeof f);
    for (int len = 1; len <= n; len += 2) {
        for (int l = 1; l + len - 1 <= n; l ++) {
            int r = l + len - 1;
            if (len == 1) f[l][r] = 1;
            else if (s[l] == s[r]) {
                for (int k = l + 2; k <= r; k += 2) {
                    if (s[k] == s[r]) {
                        f[l][r] = (f[l][r] + (ll)f[l + 1][k - 1] * f[k][r] % mod) % mod;
                    }
           
                }
            }
        }
    }
    cout << f[1][n] << endl;

    return 0;
}
posted @ 2025-10-05 18:01  michaele  阅读(9)  评论(0)    收藏  举报