题目

题目大意

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

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


思考历程

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

然而考虑到和全部互质的点可能有很多个,这样的做法可能会退化到很多……

先预处理了个\(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 <cstdio>
#include <cstring>
#include <algorithm>
#include <cassert>
//#include <cmath>
#define N 1000010
#define maxA 10000000
inline 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]<st[top-1]+1 && R[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)\)的。

其实我发现后面的这种做法有点像笛卡尔树……

这启示我们在建二叉树的过程中可以试试用栈维护最右边的那条链。

6368. 【NOIP2019模拟2019.9.25】质树的更多相关文章

  1. 6367. 【NOIP2019模拟2019.9.25】工厂

    题目 题目大意 给你一堆区间,将这些区间分成特定的几个集合,使得每个集合中的所有区间的并不为空. 求最大的每组区间的交的长度之和. 思考历程 一开始就认为这绝对是\(DP\)-- 试着找一些性质,结果 ...

  2. 6359. 【NOIP2019模拟2019.9.15】小ω的树(tree)(定期重构)

    题目描述 题解 qy的毒瘤题 CSP搞这种码农题当场手撕出题人 先按照边权从大到小建重构树,然后40%暴力修改+查找即可 100%可以定期重构+平衡规划,每次把B个询问拉出来建虚树,在虚树上暴力维护每 ...

  3. NOIP2019模拟2019.9.20】膜拜大会(外向树容斥,分类讨论)

    传送门. 题解: 我果然是不擅长分类讨论,心态被搞崩了. 注意到\(m<=n-2\),意味着除了1以外的位置不可能被加到a[1]两遍. 先考虑个大概: 考虑若存在\(x,x-1,-,2\)(有序 ...

  4. [JZOJ6359] 【NOIP2019模拟2019.9.15】小ω的树

    题目 题目大意 给你一棵树,带点权和边权. 要你选择一个联通子图,使得点权和乘最小边权最大. 支持修改点权操作. 思考历程 显然,最先想到的当然是重构树了-- 重构树就是在做最大生成树的时候,当两个联 ...

  5. 6424. 【NOIP2019模拟2019.11.13】我的订书机之恋

    题目描述 Description Input Output Sample Input 见下载 Sample Output 见下载 Data Constraint 题解 lj题卡线段树 求出每个右端点往 ...

  6. 6389. 【NOIP2019模拟2019.10.26】小w学图论

    题目描述 题解 之前做过一次 假设图建好了,设g[i]表示i->j(i<j)的个数 那么ans=∏(n-g[i]),因为连出去的必定会构成一个完全图,颜色互不相同 从n~1染色,点i的方案 ...

  7. 6364. 【NOIP2019模拟2019.9.20】养马

    题目描述 题解 一种显然的水法:max(0,-(点权-边权之和*2)) 这样会挂是因为在中途体力值可能会更小,所以考虑求走完每棵子树所需的至少体力值 考虑从子树往上推求出当前点的答案 设每棵子树从根往 ...

  8. 6362. 【NOIP2019模拟2019.9.18】数星星

    题目描述 题解 一种好想/好写/跑得比**记者还快的做法: 对所有询问排序,按照R递增的顺序来处理 维护每个点最后一次被覆盖的时间,显然当前右端点为R时的答案为所有时间≥L的点的权值之和 LCT随便覆 ...

  9. 6380. 【NOIP2019模拟2019.10.06】小w与最长路(path)

    题目 题目大意 给你一棵树,对于每一条边,求删去这条边之后,再用一条边(自己定)连接两个连通块,形成的树的直径最小是多少. 正解 首先,将这棵树的直径给找出来.显然,如果删去的边不在直径上,那么答案就 ...

随机推荐

  1. JNI原理与静态、动态注册

    前言 JNI不仅仅在NDK开发中应用,它更是Android系统中Java与Native交互的桥梁,不理解JNI的话,你就只能停留在Java Framework层.这一个系列我们来一起深入学习JNI. ...

  2. elasticsearch启动常见问题

    原文:https://blog.csdn.net/qq_22211217/article/details/80740873 一.Exception in thread "main" ...

  3. 2019华工校赛 B - 修仙时在做什么?有没有空?可以来炼丹吗?

    题目链接:https://ac.nowcoder.com/acm/contest/625/B 解法:这题其实就是求2^18个点内最近的两个点的距离.我们可以容易想到朴素解法:把每个点作为源点跑最短路取 ...

  4. VMware下Ubuntu全屏显示

      开始是这样的 完了之后应该是这样的 1.点开菜单栏的 虚拟机---------> 安装VMware Tools 安装完了之后桌面会出现一个这样的图标 双击这个DVD,进去之后左侧目录出现了 ...

  5. java多线程并发面试题

    1.多线程有什么用? (1)发挥多核CPU的优势 随着工业的进步,现在的笔记本.台式机乃至商用的应用服务器至少也都是双核的,4核.8核甚至16核的也都不少见,如果是单线程的程序,那么在双核CPU上就浪 ...

  6. Delphi 常用API 函数列表

    Delphi 常用API 函数 AdjustWindowRect 给定一种窗口样式,计算获得目标客户区矩形所需的窗口大小AnyPopup 判断屏幕上是否存在任何弹出式窗口ArrangeIconicWi ...

  7. 分布式项目pom

    <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit ...

  8. BZOJ 3998: [TJOI2015]弦论(后缀自动机)

    传送门 解题思路 \(T=0\)时就和SP7258一样,\(T=1\)时其实也差不多,只不过要把每个点原来是\(1\)的权值改为\(Right\)集合的大小. 代码 #include<iostr ...

  9. spring 对JDBC的支持 (8)

    目录 一.jdbc的简介 二.jdbcTemplate 的使用 2.1 maven 引入spring - jdbc ,c3p0 ,数据库mysql驱动 2.2 配置 数据源以及jdbcTemplate ...

  10. HA配置

    主T800 eth0 192.168.2.32备T600 eth1 192.168.2.33安装nginx yum install -y nginx 关闭主备的防火墙iptables.selinux ...