CF1025D Recovering BST 题解

CF1025D Recovering BST 题解

题意

$\ \ \ \ \ \ $ 给定一个长度为 \(n\)\(2\le n\le 700\) )的升序排列。问是否有办法用这几个数作为点权构成一棵 BST 使得这棵 BST 所有边连接的两点的点权互质。


题解

\(~~~~\) 我们知道 BST 的中序遍历一定是升序排列的,因此这个序列就是一棵二叉树的中序遍历。而当我们钦定一个升序区间中某一个节点为该区间子树的根节点时,这个子树根节点的左右子树就确定了。而把这个操作逆向过来,就是一个区间 DP 的过程。

\(~~~~\) 本题有一些不一样,看到 \(n\) 很小,可以往 \(\mathcal{O(n^3)}\) 的算法去想。

\(~~~~\) 如果一开始没有想到,可以考虑用暴力一点的算法优化上来。不难想到一个 \(n^4\) 的 DP ,设: \(dp_{i,j,k}\) 表示区间 \([l,r]\) 以整个序列的第 \(k\) 个元素为根时该区间的合法性。

\(~~~~\) 很容易想到如何转移,用 \(n^2\) 枚举区间,\(n\) 枚举当前区间子树的根节点,\(n\) 枚举左右区间是否有满足条件的根节点即可。

\(~~~~\) 那我们怎么优化这个过程呢?发现 \(n^2\) 的枚举区间肯定优化不动,那就往另外的两个过程想。

\(~~~~\) 发现一个区间 \([l,r]\) 其父节点只可能是 \(l-1\)\(r+1\) 。可以利用这个性质进行 DP

\(~~~~\) 正解部分: 定义: \(dp_{i,j,k}\) ,当 \(k=0\) 时表示 \([l,r-1]\) 的父节点是 \(r\) 的合法性,当 \(k=1\) 时表示 \([l+1,r]\) 的父节点是 \(l\)。考虑一个已知的状态 \(dp_{l,r,k}\) 能扩展出的状态,可以得到转移方程。

\[\Large dp_{l-1,r,1}=dp_{l,k,0} ~ \& ~ dp_{k,r,1} ~ \& ~ (a_{l-1},a_{k}) \not= 1 \]

\[\Large dp_{l,r+1,0}=dp_{l,k,0} ~ \& ~ dp_{k,r,1} ~ \& ~ (a_{r+1},a_{k}) \not =1 \]

\(~~~~\) 初值:\(dp_{i,i,0},dp_{i,i,1} (1 \le i \le n)\) 均为 \(1\)。答案:当某次转移到区间 \([1,n]\) 时满足上面两个转移方程的前两个条件即可。

\(~~~~\) 然后大力转移就可以了,另外还要注意一些细节,具体见代码。


代码

#include <cstdio>
#include <algorithm>
using namespace std;
int gcd(int a,int b)
{
	if(b==0) return a;
	else return gcd(b,a%b);
}
int arr[705];
bool f[705][705],dp[705][705][2];//dp[l][r][k]:区间 [l,r] 在父节点为 r(k=0)时的合法性与在父节点为 l(k=1)时的合法性 
int main() {
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&arr[i]),dp[i][i][0]=dp[i][i][1]=1;
	for(int i=1;i<=n;i++)
	{
		for(int j=i+1;j<=n;j++)
		{
			if(gcd(arr[i],arr[j])!=1) f[i][j]=f[j][i]=true;// n^2预处理一下,不然每次都有 log 也受不起。 
		}
	}
	for(int l=n;l>=1;l--)
	for(int r=l;r<=n;r++)
	for(int k=l;k<=r;k++)
	{
		if(dp[l][k][0]&&dp[k][r][1])
		{
			if(l==1&&r==n)
			{
				puts("Yes");
				return 0;	
			}
			if(f[l-1][k]) dp[l-1][r][1]=true;
			if(f[r+1][k]) dp[l][r+1][0]=true;
		}
	}
	puts("No");
	return 0;
}
posted @ 2020-08-11 20:19  Azazеl  阅读(182)  评论(0编辑  收藏  举报