[CF1025D] Recovering BST - 区间dp
Description
给定一棵二叉搜索树 (n le 700) 的所有点权,构造二叉树满足任意两个直接相邻的点的权值的 GCD 不是 1
Solution
这种关于二叉搜索树中序遍历的 DP 问题,设状态时忽略根而考虑区间,因为这个区间作为一个子树,它的父亲一定是区间左端点的左边的点或者区间右端点的右边的点
设 (f[i][j]) 表示以 ([i,j-1]) 作为 j 的左孩子是否合法
设 (g[i][j]) 表示以 ([i+1,j]) 作为 i 的右孩子是否合法
转移时,考虑一个区间 ([l,r]),枚举它的根 (k),需要满足 (f[i][k]) 并且 (g[k][j])
如果此时 (k) 与 (l-1) 相连是合法的,那么就可以转移到 ([l-1,r])
如果此时 (k) 与 (r+1) 相连是合法的,那么就可以转移到 ([l,r+1])
总之,设状态时设的是“半棵子树”的合法性,转移时首先考虑一个区间可能有哪些根,然后分别考虑这些点为根时,向外的转移情况
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 705;
// 设 $f[i][j]$ 表示以 $[i,j-1]$ 作为 j 的左孩子是否合法
// 设 $g[i][j]$ 表示以 $[i+1,j]$ 作为 i 的右孩子是否合法
int f[N][N], g[N][N], a[N], n, x[N][N];
signed main()
{
ios::sync_with_stdio(false);
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1; i <= n; i++)
f[i][i] = g[i][i] = 1;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (__gcd(a[i], a[j]) > 1)
x[i][j] = 1;
for (int i = n; i >= 1; i--)
{
for (int j = i; j <= n; j++)
{
int l = i, r = j;
for (int k = l; k <= r; k++)
{
if (f[i][k] && g[k][j])
{
if (l > 1 && x[l - 1][k])
g[l - 1][r] = 1;
if (r < n && x[r + 1][k])
f[l][r + 1] = 1;
}
}
}
}
for (int i = 1; i <= n; i++)
{
if (f[1][i] && g[i][n])
{
cout << "Yes" << endl;
return 0;
}
}
cout << "No" << endl;
}