zoukankan      html  css  js  c++  java
  • 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(nlg n))的做法:从左右两边同时寻找,找到之后就递归。这样看上去似乎没有什么优化,实际上,相当于寻找的时间复杂度小于等于当前区间长度的一半,然后继续递归。
    可以看做这个小区间递归了,大区间继续找,那最后就是大区间在区间长度的时间复杂度内,分成了若干个长度小于等于总长的一半的小区间递归下去。
    所以就只有(O(lg n))层。

    然后我发现这就是题解做法之一……另一种做法比较强大,在预处理之后就是(O(n))的(我最后就是打了这种做法)。
    用一个栈来维护最右边的一条链,栈顶就是深度最大的那个点。
    设栈顶为(b),栈顶下面的那一个为(a)
    考虑增量法,每次新增一个(i)进去。
    然后看看能不能通过旋转使得答案更优。
    能够旋转的必要条件是(L_i< a+1)(因为(a+1)是先前(b)子树区间的左端点)
    使得更优的必要条件是(R_igeq 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
    ");
    			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(nlg n))的。
    其实我发现后面的这种做法有点像笛卡尔树……
    这启示我们在建二叉树的过程中可以试试用栈维护最右边的那条链。

  • 相关阅读:
    Nagios HTTP WARNING: HTTP/1.1 403 Forbidden
    nagios监控的安装
    Linux里使用rz和sz命令
    Mariadb安装
    ubuntu16.04下载地址
    安装.msi格式安装包
    英文语法检测工具
    正确引用R及R包
    West world 西部世界
    Altered Carbon 碳变/副本
  • 原文地址:https://www.cnblogs.com/jz-597/p/11593335.html
Copyright © 2011-2022 走看看