题目

题目大意

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

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


思考历程

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

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

先预处理了个\(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. 【Luogu】【关卡2-2】交叉模拟(2017年10月)

    任务说明:这里也是模拟,但是会混有些别的部分.思维难度不大,但是编写起来会有些难度.

  2. Goaccess的简单使用

    goaccess了,它是一个日志分析工具,并不只是为nginx使用的,你也可以用它来分析apache,具有解析速度快,使用简单,能生成json,html,csv等特点. 1.goaccess的基本安装 ...

  3. KeepLived + nginx 高可用

    . 环境准备 1. VMware; 2. 4台CentOs7虚拟主机:192.168.122.217, 192.168.122.165 3. 系统服务:LVS, Keepalived 4. Web服务 ...

  4. 【LeetCode 9】回文数

    题目链接 [题解] 还是要注意,取反的时候,-2^31 取反的话会爆掉Int..(因为int的正数最多到2^31-1) [代码] class Solution { public: bool isPal ...

  5. 管理员技术(一):装机预备技能、安装一台RHEL7虚拟机、使用RHEL7图形桌面、Linux命令行基本操作

    一.装机预备技能 问题: 本例要求安装一台可用的KVM服务器:      1> RHEL与CentOS系统有什么关联?      2> 第2块SCSI硬盘的第3个逻辑分区,Linux如何表 ...

  6. AcWing 286. 选课 (树形依赖分组背包)打卡

    有依赖的背包 首先依赖的概念,就是一个东西依附与一个东西之上,我们想买附品的话必须要把主品先买下来,这个可以先做下这道题 https://www.cnblogs.com/Lis-/p/11047466 ...

  7. P1064 金明的预算方案 (分组背包稍稍变形)

    题目描述 金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间金明自己专用的很宽敞的房间.更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过NN元钱就行”. ...

  8. linux进阶之路(三):vi/vim编辑器

    所有Linux都会内置vi,vim是vi的增强版本,被誉为"编辑之神",玩转vim可以让你完全脱离鼠标. vim可以分为两种模式: 普通模式:使用vim 文件名,进入普通模式.普通 ...

  9. html01. <!DOCTYPE html>

    解释 在HTML文档初,往往会有这么一句话<!DOCTYPE html>,它是html5标准网页声明,全称为Document Type HyperText Mark-up Language ...

  10. Servilet初步

    以http://locahost:8080/......开头,或者以/开头,都是绝对路径以路径开头:相对路径 路径/路径 Servlet执行流程:(只用自己编写执行的代码,执行的细节全是tomcat封 ...