博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
6368. 【NOIP2019模拟2019.9.25】质树
阅读量:5291 次
发布时间:2019-06-14

本文共 3339 字,大约阅读时间需要 11 分钟。

题目大意

有个二叉树,满足每个点跟它的所有祖先互质。

给出二叉树的中序遍历的点权,还原一种可能的方案。


思考历程

首先想到的当然是找到一个跟全部互质的点作为根,然后左右两边递归下去处理……

然而考虑到和全部互质的点可能有很多个,这样的做法可能会退化到很多……
先预处理了个\(L_i\)\(R_i\)表示\(i\)左边第一个和\(i\)不互质的位置和右边第一个和\(i\)不互质的点。
这个东西怎么预处理就不用说吧……
(我估计正解肯定也要处理这东西)
然后就是乱搞……
想不出正解,于是打了个\(O(n^3)\)的区间\(DP\)……
在时间复杂度上似乎连一分都没有……


正解

在比赛的后期我发现了一个很有用的结论:

如果一个区间可以成为一棵合法的子树,那么这个区间的所有子区间都可以成为一棵合法的子树。
这个是比较显然的,原因是什么自己脑补……我还拍过了几个数据……
(然而我在比赛的最后都不知道该怎么用……)
然后YMQ告诉我一个性质:如果一个区间中选择任意一个跟其它所有数互质的点,然后递归下去处理,处理完之后发现不行,那么选择其它的和所有数互质的点处理也不行。
回去一个晚上想到了证明:
上面的那个结论反过来就是说,如果一个区间不能成为一棵合法的子树,那么所有包含它的区间都不能成为一棵合法的子树。
假设这个区间是\([l,r]\),其中的一个和其它所有数互质的点为\(x\)
如果处理之后不行,则必然是\([l,x-1]\)\([x+1,r]\)不行。
所以包含\([l,x-1]\)\([x+1,r]\)的所有区间都不行啊……这区间也自然包括\([l,r]\)

接下来就有一种比较简单又自然的做法:随便找一个和其它数互质的点,作为子树的根,左右递归下去。显然时间复杂度是\(O(n^2)\)的。

回去睡了一个晚上,想到了\(O(n\lg n)\)的做法:从左右两边同时寻找,找到之后就递归。这样看上去似乎没有什么优化,实际上,相当于寻找的时间复杂度小于等于当前区间长度的一半,然后继续递归。
可以看做这个小区间递归了,大区间继续找,那最后就是大区间在区间长度的时间复杂度内,分成了若干个长度小于等于总长的一半的小区间递归下去。
所以就只有\(O(\lg n)\)层。

然后我发现这就是题解做法之一……另一种做法比较强大,在预处理之后就是\(O(n)\)的(我最后就是打了这种做法)。

用一个栈来维护最右边的一条链,栈顶就是深度最大的那个点。
设栈顶为\(b\),栈顶下面的那一个为\(a\)
考虑增量法,每次新增一个\(i\)进去。
然后看看能不能通过旋转使得答案更优。
能够旋转的必要条件是\(L_i< a+1\)(因为\(a+1\)是先前\(b\)子树区间的左端点)
使得更优的必要条件是\(R_i\geq R_b\),这就意味着右儿子可以添加更多的点,当然更优。
如果可以成功旋转就一直试着旋转下去,直到变成根或者不优。
搞完之后,判断一下是否\(i<R_b\)。如果不是,那就impossible
按照我的理解,由于前面的策略已经保证了向右延伸得最多,所以如果依然不能容纳\(i\),那么证明肯定不合法。
这样一直建下去就行了。


代码

由于oj出锅了,所以我也不知道这个代码对不对……

using namespace std;#include 
#include
#include
#include
//#include
#define N 1000010#define maxA 10000000inline int input(){ char ch=getchar(); while (ch<'0' || ch>'9') ch=getchar(); int x=0; do{ x=x*10+ch-'0'; ch=getchar(); } while ('0'<=ch && ch<='9'); return x;}int n,a[N];int p[maxA],np;int mnp[maxA+10];int lst[maxA+10];int L[N],R[N];int st[N],top;int fa[N],son[N][2];int main(){// freopen("in.txt","r",stdin); freopen("tree.in","r",stdin); freopen("tree.out","w",stdout); for (int i=2;i<=maxA;++i){ if (!mnp[i]){ p[++np]=i; mnp[i]=i; } for (int j=1;j<=np && i*p[j]<=maxA;++j){ mnp[i*p[j]]=p[j]; if (!(i%p[j])) break; } } n=input(); for (int i=1;i<=n;++i) a[i]=input(); for (int i=1;i<=n;++i){ int t=a[i]; while (t!=1){ int x=mnp[t]; L[i]=max(L[i],lst[x]); lst[x]=i; while (!(t%x)) t/=x; } } for (int i=1;i<=maxA;++i) lst[i]=n+1; for (int i=n;i>=1;--i){ R[i]=n+1; int t=a[i]; while (t!=1){ int x=mnp[t]; R[i]=min(R[i],lst[x]); lst[x]=i; while (!(t%x)) t/=x; } } st[top=1]=1; for (int i=2;i<=n;++i){ if (R[st[top]]<=i){ printf("impossible\n"); return 0; } while (top && L[i]
=R[st[top]]){ son[st[top]][1]=son[i][0]; son[i][0]=st[top]; top--; } son[st[top]][1]=i; st[++top]=i; } for (int i=1;i<=n;++i) fa[son[i][0]]=fa[son[i][1]]=i; for (int i=1;i<=n;++i) printf("%d ",fa[i]); return 0;}

总结

前面的那种分治做法应该是一个套路。

也就是\(T(n)=T(x)+T(n-x)+min(x,n-x)\)这种形式的分治,它的时间复杂度也是\(O(n\lg n)\)的。
其实我发现后面的这种做法有点像笛卡尔树……
这启示我们在建二叉树的过程中可以试试用栈维护最右边的那条链。

转载于:https://www.cnblogs.com/jz-597/p/11593335.html

你可能感兴趣的文章
#ifndef #define #endif
查看>>
卷积神经网络知识链接
查看>>
java简介
查看>>
浮动、定位
查看>>
js细节
查看>>
SQL语句大全
查看>>
java中的变量
查看>>
css背景样式
查看>>
JavaScript介绍
查看>>
js中函数与对象的使用
查看>>
Date对象及toString方法
查看>>
正则表达式
查看>>
异常及throw、与throws的介绍
查看>>
js数组
查看>>
java运算符
查看>>
mysql简介
查看>>
java数组
查看>>
java二维数组
查看>>
IO流File
查看>>
java中的方法(函数)
查看>>