BZOJ5200FactorFreeTree——启发式分治

题目链接,F题
大意:一棵 F a c t o r F r e e T r e e FactorFreeTree FactorFreeTree树,满足对于每个点它的所有祖先都与他互质。现在给你一个序列,问你是否有一棵树,满足它的中序遍历是给出的序列,并且它是一棵 F a c t o r F r e e T r e e FactorFreeTree FactorFreeTree
如果有,输出任意一棵。输出时输出每个点的父亲,没有父亲则输 0 0 0。否则输出 − 1 -1 1

给出中序遍历,让我们建树,显然可以采取分治的策略。每次在一段区间里选一个点 x x x做根,然后把这段区间分为 [ l , x − 1 ] [l,x-1] [l,x1] [ x + 1 , r ] [x+1,r] [x+1,r]两段区间。继续分治下去即可。
但这道题有互质的限制。一个点能当根,那么它的子树里的点必然都与它互质。那么转化到区间里,就是 [ l , r ] [l,r] [l,r]区间里的所有点都与它互质。
如果预处理出 L [ x ] L[x] L[x] R [ x ] R[x] R[x]表示 x x x左边(和右边)第一个和它不互质的数的位置,那么只要满足 L [ x ] &lt; = l &amp; &amp; R [ x ] &gt; = r L[x]&lt;=l\&amp;\&amp;R[x]&gt;=r L[x]<=l&&R[x]>=r x x x就可以作为区间的根。
但是普通的分治会被卡,因为这种题普通的分治上界是 O ( n 2 ) O(n^2) O(n2)的。所以我们又用启发式分裂,也就是从左右两端向中间推进。这样可以保证复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)
#include<bits/stdc++.h>
#define MAXN 1000005
using namespace std;
int read(){
	char c;int x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
	while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
}
void print(int x){
	if(x/10) print(x/10);
	putchar(x%10+'0');
}
int n,top,a[MAXN],vis[MAXN*10],pri[MAXN*10],p[MAXN*10],l[MAXN],r[MAXN],lst[MAXN*10],fa[MAXN];
int dfs(int L,int R,int f){
	if(L>R) return 1;
	if(L==R){fa[L]=f;return 1;}
	int wr=L,un=R;
	for(int i=L;i<=R;i++){
		if(i&1){if(l[wr]<=L&&r[wr]>=R){fa[wr]=f;return dfs(L,wr-1,wr)&dfs(wr+1,R,wr);}wr++;}
		else{if(l[un]<=L&&r[un]>=R){fa[un]=f;return dfs(L,un-1,un)&dfs(un+1,R,un);}un--;}
	}
	return 0;
}
int main()
{
	n=read();vis[1]=1;vis[2]=0;
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=2;i<=10000000;i++){
		if(!vis[i]) pri[++top]=i,p[i]=i;
		for(int j=1;j<=top&&i*pri[j]<=10000000;j++){
			vis[i*pri[j]]=1;p[i*pri[j]]=pri[j];
			if(i%pri[j]==0) break;
		}
	}
	for(int i=1;i<=n;i++){
		int x=a[i];
		while(x>1){
			int y=p[x];
			l[i]=max(l[i],lst[y]+1);
			lst[y]=i;
			while(x%y==0) x/=y;
		}
	}
	memset(lst,0,sizeof(lst));memset(r,63,sizeof(r));
	for(int i=n;i>=1;i--){
		int x=a[i];
		while(x>1){
			int y=p[x];
			r[i]=min(r[i],(lst[y]?lst[y]:n+1)-1);
			lst[y]=i;
			while(x%y==0) x/=y;
		}
	}	
	if(!dfs(1,n,0)) puts("impossible");
	else for(int i=1;i<=n;i++) print(fa[i]),putchar(' ');
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值