6368. 【NOIP2019模拟2019.9.25】质树

题目

题目大意

有个二叉树,满足每个点跟它的所有祖先互质。
给出二叉树的中序遍历的点权,还原一种可能的方案。


思考历程

首先想到的当然是找到一个跟全部互质的点作为根,然后左右两边递归下去处理……
然而考虑到和全部互质的点可能有很多个,这样的做法可能会退化到很多……
先预处理了个\(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\)进去,如果\(R_b\leq i\),那肯定不行(显然这时候\(b=i-1\))。
然后将\(i\)加进去,并且看看能不能通过旋转使得答案更优。
能够旋转的必要条件是\(L_i< a+1\)(因为\(a+1\)是先前\(b\)子树区间的左端点)
使得更优的必要条件是\(R_i\geq R_b\),这就意味着右儿子可以添加更多的点,当然更优。
如果可以成功旋转就一直试着旋转下去,直到变成根或者不优。
显然这样可以保证整棵树是合法的。


代码

由于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)\)的。
其实我发现后面的这种做法有点像笛卡尔树……
这启示我们在建二叉树的过程中可以试试用栈维护最右边的那条链。

猜你喜欢

转载自www.cnblogs.com/jz-597/p/11593335.html
今日推荐