题目
题目概要
给出一棵树的中序遍历,问,能否还原出这棵树,满足树上任意有祖先关系的不同两点
a
,
b
a,b
a,b 有
gcd
(
v
a
,
v
b
)
=
1
\gcd(v_a,v_b)=1
gcd(va,vb)=1 。如果有,输出这棵树。
数据范围与提示
n
≤
1
0
6
n\le 10^6
n≤106 且
v
i
≤
1
0
7
v_i\le 10^7
vi≤107 。时间限制
6
s
6\rm s
6s 。
思路
不难想到一个
d
p
\tt dp
dp 的思路,
f
(
l
,
r
)
f(l,r)
f(l,r) 表示中序遍历结果中第
l
l
l 个到第
r
r
r 个能否构建出 “互质树”。
f
(
l
,
r
)
=
max
i
=
l
r
f
(
l
,
i
−
1
)
×
f
(
i
+
1
,
r
)
×
[
∀
j
,
gcd
(
v
i
,
v
j
)
=
1
]
f(l,r)=\max_{i=l}^{r}f(l,i-1)\times f(i+1,r)\times [\forall j,\;\gcd(v_i,v_j)=1]
f(l,r)=i=lmaxrf(l,i−1)×f(i+1,r)×[∀j,gcd(vi,vj)=1]
这个是
O
(
n
4
)
\mathcal O(n^4)
O(n4) 的,直接起飞。考虑优化。比如,我们容易发现
f
(
l
−
1
,
r
)
=
1
∨
f
(
l
,
r
+
1
)
=
1
⇒
f
(
l
,
r
)
=
1
f(l-1,r)=1\vee f(l,r+1)=1 \Rightarrow f(l,r)=1
f(l−1,r)=1∨f(l,r+1)=1⇒f(l,r)=1
正确性似乎是显然的,我还是简要说两句。考虑 f ( l , r + 1 ) f(l,r{\rm+}1) f(l,r+1) 对应的 “互质树”,第 r + 1 r+1 r+1 号节点必然没有右子树(否则其右子树是中序遍历更靠后的),删掉 r + 1 r+1 r+1 节点并连接其父节点与其左儿子,生成的就是 f ( l , r ) f(l,r) f(l,r) 对应的树。得到的新树肯定仍然是合法的。
你可能没意识到这句话意味着什么。那我告诉你,这就是说,如果当前区间可行,那么所有子区间都可行。所以,更新 f ( l , r ) f(l,r) f(l,r) 时,只需要任取一个合法的 i i i,如果不行,就说明 f ( l , r ) = 0 f(l,r)=0 f(l,r)=0 了。
现在的问题是,怎么找 i i i 呢?想 O ( 1 ) \mathcal O(1) O(1) 比较困难。一个比较简单的方法是利用启发式合并,在 O [ min ( i , n − i ) ] \mathcal O[\min(i,n-i)] O[min(i,n−i)] 的复杂度内完成即可。答案就是 两边向中间找。
判断就很简单了,每个数质因数分解一下,利用 O ( V ) \mathcal O(V) O(V) 线性筛后 O ( n log V ) \mathcal O(n\log V) O(nlogV) 分解。对于每种质因数,把数组扫一遍。而后我们求得每个数字的控制范围。而后可以 O ( 1 ) \mathcal O(1) O(1) 判断 i i i 是否为根。
递归的复杂度
T
(
n
)
=
T
(
i
)
+
T
(
n
−
i
)
+
O
[
min
(
i
,
n
−
i
)
]
=
O
(
n
log
n
)
T(n)=T(i)+T(n-i)+\mathcal O[\min(i,n-i)]=\mathcal O(n\log n)
T(n)=T(i)+T(n−i)+O[min(i,n−i)]=O(nlogn)
总复杂度 O ( V + n log V + n log n ) \mathcal O(V+n\log V+n\log n) O(V+nlogV+nlogn) 。
代码
#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
inline int readint(){
int a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
const int MaxV = 10000001;
int nxt[MaxV], least[MaxV];
vector<int> primes;
void sieve(int n = MaxV-1){
for(int i=2; i<=n; ++i)
nxt[i] = 1, least[i] = i;
for(int i=2,len=0; i<=n; ++i){
if(least[i] == i) ++ len,
primes.push_back(i);
for(int j=0; j<len&&primes[j]<=n/i; ++j){
nxt[i*primes[j]] = i;
least[i*primes[j]] = primes[j];
if(i%primes[j] == 0){
nxt[i*primes[j]] = nxt[i];
break;
}
}
}
}
vector<int> v[MaxV];
const int MaxN = 1000001;
int L[MaxN], R[MaxN];
int fa[MaxN];
int solve(int l,int r){
if(l == r) return l; // leaf
if(l > r) return 0;
for(int i=0; l+i<=r-i; ++i){
if(L[l+i] < l && r < R[l+i]){
int lson = solve(l,l+i-1);
int rson = solve(l+i+1,r);
if(!(~lson) || !(~rson))
return -1; // no such tree
fa[lson] = fa[rson] = l+i;
return l+i; // root
}
if(L[r-i] < l && r < R[r-i]){
int lson = solve(l,r-i-1);
int rson = solve(r-i+1,r);
if(!~lson || !~rson)
return -1; // no such tree
fa[lson] = fa[rson] = r-i;
return r-i; // root
}
}
return -1; // no such tree
}
int main(){
sieve(); // prepare
int n = readint();
for(int i=1; i<=n; ++i){
int x = readint();
for(; x!=1; x=nxt[x])
v[least[x]].push_back(i);
L[i] = 0, R[i] = n+1; // (L,R)
}
for(int i=0; i<MaxV; ++i){
int len = v[i].size();
for(int j=0; j+1<len; ++j){
R[v[i][j]] = min(R[v[i][j]],v[i][j+1]);
L[v[i][j+1]] = max(L[v[i][j+1]],v[i][j]);
}
}
if(solve(1,n) == -1)
puts("impossible");
else{
for(int i=1; i<=n; ++i)
printf("%d ",fa[i]);
putchar('\n');
}
return 0;
}