题目大意:给出某一颗带点权树的中序遍历序列,问是否存在一颗对应的带点权树满足条件:每个节点的子孙节点的权值都和该节点的权值互质。如果存在则输出符合条件的树的每个节点父亲节点。
这题有数个关键点,第一个是利用类似于线性筛法的算法得到每个点权值有哪些质因子,利用线性筛法可以达到均摊每个权值log(质数的数量)的效率;
第二个是对于当前序列,如果存在多个点和其他权值都互质,取其中任意一个都可以得到解,简要证明的话可以类比动态树的旋转操作,如果取点x可以得到解,则利用旋转操作,可以在保证不改变中序遍历的情况下改变树的结构得到以另一个点y
作为根节点的解,所以,不论取序列中哪一个和其他值互质的点作为当前的根节点,其结果都一样;
第三个是如何快速判断一个点和区间
[L,R]
[
L
,
R
]
内所有点都互质。其方法O(n)预处理每个元素x左侧最近的,和其拥有公共质因数的值的位置LL[x],以及右侧最近的和其拥有公共质因数的位置RR[x],如果
LL[x]<L
L
L
[
x
]
<
L
且
RR[x]>R
R
R
[
x
]
>
R
则元素x与区间
[L,R]
[
L
,
R
]
中的所有值互质
第四个是在递归处理子序列的时候,如果直接从左到右依次判断的话,最坏会达到O(n^2)的复杂度,但是如果同时从两侧搜索则会较大地提高效率。
代码:
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#define MAXN 1100000
#define MAXM 11100000
using namespace std;
inline void read(int &x) {
char ch;
bool flag = false;
for (ch = getchar(); !isdigit(ch); ch = getchar())if (ch == '-') flag = true;
for (x = 0; isdigit(ch); x = x * 10 + ch - '0', ch = getchar());
x = flag ? -x : x;
}
inline void write(int x) {
static const int maxlen = 100;
static char s[maxlen];
if (x < 0) { putchar('-'); x = -x;}
if (!x) { putchar('0'); return; }
int len = 0; for (; x; x /= 10) s[len++] = x % 10 + '0';
for (int i = len - 1; i >= 0; --i) putchar(s[i]);
}
int n;
int a[ MAXN ];
int su[ MAXM ],tot;
bool vis[ MAXM ];
bool IN[ MAXM ];
int num[ MAXN ][ 10 ],total[ MAXN ];
int lsh_cnt=0;
int lsh[ MAXM ];
int cnt=0;
int LL[ MAXN ], RR[ MAXN ];
int fa[ MAXN ];
int pre[ MAXM ], nex[ MAXM ];
int doit(int l,int r){
//printf("%d %d\n",l,r);
if ( l>r )
return 0;
if ( l==r )
return l;
int op=0;
int li=l,ri=r;
while (li<=ri)
{
int i;
if ( op==0 )
i=li++;
else
i=ri--;
op=1-op;
if ( ( LL[i]<l ) && ( RR[i]>r ) )
{
int tmp=doit(l,i-1);
if ( tmp==-1 )
return -1;
fa[tmp]=i;
tmp=doit(i+1,r);
if ( tmp==-1 )
return -1;
fa[tmp]=i;
return i;
}
}
return -1;
}
void prepare(){
for (int i=2;i<MAXM;i++)
{
if (!vis[i])
su[++tot]=i;
for (int j=1;j<=tot;j++)
{
if ( 1ll*su[j]*i>=MAXM )
break;
vis[ su[j]*i ] = 1;
if ( i%su[j]==0 )
break;
}
}
}
int main(){
prepare();
read(n);
for (int i=1;i<=n;i++)
{
read(a[i]);
if (!IN[a[i]])
{
IN[a[i]]=1;
lsh_cnt++;
lsh[a[i]]=lsh_cnt;
}
}
for (int i=1;i<=tot;i++)
{
for (int j=su[i];j<MAXM;j+=su[i])
if ( IN[ j ] )
num[ lsh[j] ][ ++total[lsh[j]] ]=i;
}
for (int i=1;i<=n;i++)
a[i]=lsh[a[i]];
for (int i=1;i<=n;i++)
{
int MAX=0;
for (int j=1;j<=total[a[i]];j++)
{
if ( pre[num[a[i]][j]] )
MAX=max(MAX,pre[num[a[i]][j]]);
pre[num[a[i]][j]]=i;
}
LL[i]=MAX;
}
for (int i=n;i>=1;i--)
{
int MIN=n+1;
for (int j=1;j<=total[a[i]];j++)
{
if ( nex[num[a[i]][j]] )
MIN=min(MIN,nex[num[a[i]][j]]);
nex[num[a[i]][j]]=i;
}
RR[i]=MIN;
}
int tmp=doit(1,n);
if (tmp==-1)
{
puts("impossible");
return 0;
}
for (int i=1;i<n;i++)
{
write(fa[i]);
printf(" ");
}
printf("%d\n",fa[n]);
return 0;
}