[题解] [CF1025D] Recovering BST
题面
题解
如果不是二叉搜索树其实这题就是最小生成树板子题
设 \(f[l][r][k]\) 为 \([l, r]\) 这段区间是否可以以 \(k\) 为根构成一棵合法的树
转移就是
\[f[l][r][k] |= f[l][mid][i] \& f[mid + 1][r][j], mid \in [l, r - 1]
\]
这个方程是 \(O(n^5)\) 的, 考虑优化
考虑到这样一个性质, 我们还是以 \([l, r]\) 中根为 \(k\) 为例
我们只需要使 \([l, k - 1]\) , \([k + 1, r]\) 这两段区间可以构成合法的一棵树
并且这两棵树的树根可以和 \(k\) 相连就可以了
设 \(g[l][r]\) 为 \([l, r]\) 这一段区间是否可以以 \(r\) 为根构成一棵合法的树
\(h[l][r]\) 为 \([l, r]\) 这一段区间是否可以以 \(l\) 为根构成一棵合法的树
那么 \(f[l][r][k] = 1\) 就是 \(g[l][k] = 1\), \(h[k][r] = 1\)
那么我们就将求 \(f\) 转化为了求 \(g, h\)
而 \(g\) 的转移方程又是
\[g[l][r] |= g[l][k] \& h[k][r - 1]
\]
\(k\) 在 \([l, r - 1]\) 中且 \(k\) 可以与 \(r\) 连边
\(h\) 的转移方程类似, 这里就不再赘述了
那么这样答案就是 \(O(n^3)\) 的了
答案合法只需要 \([1, n]\) 中存在一个 \(i\) 使得 \(g[1][i] = h[i][n] = 1\)
Code
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
const int N = 705;
using namespace std;
int n, a[N];
bool f[N][N], g[N][N], vis[N][N];
template < typename T >
inline T read()
{
T x = 0, w = 1; char c = getchar();
while(c < '0' || c > '9') { if(c == '-') w = -1; c = getchar(); }
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * w;
}
int gcd(int n, int m) { return m ? gcd(m, n % m) : n; }
int main()
{
n = read <int> ();
for(int i = 1; i <= n; i++)
a[i] = read <int> ();
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(i != j) vis[i][j] = gcd(a[i], a[j]) != 1;
for(int r, k = 2; k <= n; k++)
for(int l = 1; l + k - 1 <= n; l++)
{
r = l + k - 1;
for(int p = l; p < r; p++)
if(vis[p][r]) f[l][r] |= f[l][p] & g[p][r - 1];
for(int p = r; p > l; p--)
if(vis[l][p]) g[l][r] |= f[l + 1][p] & g[p][r];
}
for(int i = 1; i <= n; i++)
if(f[1][i] & g[i][n]) { puts("Yes"); return 0; }
puts("No");
return 0;
}
一点小小的收获
有的时候可以把一个式子拆成两部分, 对两部分分开 DP
因为拆开之后某一个值不需要再枚举了, 所以可以优化复杂度