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;
}

浙公网安备 33010602011771号