[题解] [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
因为拆开之后某一个值不需要再枚举了, 所以可以优化复杂度

posted @ 2020-04-27 22:18  ztlztl  阅读(95)  评论(0编辑  收藏  举报