抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

LOJ 6066

题解

答案具有单调性,于是二分 \(k\),问题转化成:

是否存在两个点 \(u,v\) 满足 \(u\)\(k-\) 子树与 \(v\)\(k-\) 子树形态相同。

解决这样的问题可以使用哈希,那么我们需要找到一种哈希的方式使得“形态”相同的树的哈希值相同,“形态”不同的树的哈希值不同。

由于节点标号与树的形态无关,儿子的顺序与树的形态有关,容易想到括号序列。于是问题变成:

在以 \(u\) 为根的子树对应的括号序列中,删去所有与 \(u\) 距离 \(k+1\) 的点为根的子树对应的括号序列后,求哈希值。

由于每个点只会在 \(k+1\) 级祖先处被遍历,所以求所有 \(k-\) 子树的哈希值是 \(O(n)\) 的。

求每个点的 \(k+1\) 级祖先可以使用倍增做到 \(O(n\log n)\) 或用长链剖分做到 \(O(n)\)

总时间复杂度 \(O(n\log^2 n)\)

代码

我使用了双哈希 + set 的方式进行判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <vector>
#include <set>
int read(){
register int x = 0;
register char f = 1, ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f ^= 1;
for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ '0');
return f ? x : -x;
}
namespace Double_Hash{
static const int N = 200005, B1 = 233, P1 = 382538579, B2 = 331, P2 = 952959323;
int pw1[N], pw2[N];
struct node{
int H1, H2;
node(int _H1 = 0, int _H2 = 0){ H1 = _H1, H2 = _H2; }
bool operator == (const node &rhs) const {
return H1 == rhs.H1 && H2 == rhs.H2;
}
bool operator < (const node &rhs) const {
return H1 < rhs.H1 || (H1 == rhs.H1 && H2 < rhs.H2);
}
node operator + (const char ch) const {
return node((1ll * H1 * B1 + ch) % P1, (1ll * H2 * B2 + ch) % P2);
}
node operator + (const node &rhs) const {
return node((H1 + rhs.H1) % P1, (H2 + rhs.H2) % P2);
}
node operator - (const node &rhs) const {
return node((H1 - rhs.H1 + P1) % P1, (H2 - rhs.H2 + P2) % P2);
}
node operator << (const int x) const {
return node(1ll * H1 * pw1[x] % P1, 1ll * H2 * pw2[x] % P2);
}
}h[N];
void pre(int n = N - 1){
pw1[0] = 1, pw2[0] = 1;
for (register int i = 1; i <= n; ++i)
pw1[i] = 1ll * pw1[i - 1] * B1 % P1, pw2[i] = 1ll * pw2[i - 1] * B2 % P2;
}
node hash(int l, int r){
return h[r] - (h[l - 1] << (r - l + 1));
}
void build(char *a, int n){
for (register int i = 1; i <= n; ++i) h[i] = h[i - 1] + a[i];
}
}
using namespace Double_Hash;
#define N 100005
int n, fa[N][20], idx, l[N], r[N], dis[N];
char a[N << 1];
std :: vector<int> son[N], po[N];
std :: set<node> S;
void dfs(int u){
dis[u] = 0, l[u] = ++idx, a[idx] = 23;
for (register int i = 1; i <= 18; ++i)
fa[u][i] = fa[fa[u][i - 1]][i - 1];
for (register int i = 0, v; i < son[u].size(); ++i)
v = son[u][i], fa[v][0] = u, dfs(v), dis[u] = std :: max(dis[u], dis[v] + 1);
r[u] = ++idx, a[idx] = 97;
}
int kth_fa(int u, int k){
for (register int i = 0; i <= 18; ++i)
if (k >> i & 1) u = fa[u][i];
return u;
}
bool check(int k){
for (register int i = 1; i <= n; ++i) po[i].clear();
for (register int i = 1; i <= n; ++i)
po[i].push_back(l[i]), po[i].push_back(r[i] + 1);
for (register int i = 1; i <= n; ++i){
int p = kth_fa(i, k + 1);
if (p) po[p].push_back(l[i]), po[p].push_back(r[i] + 1);
}
for (register int i = 1; i <= n; ++i)
std :: sort(po[i].begin(), po[i].end());
S.clear();
for (register int i = 1; i <= n; ++i)
if (dis[i] >= k){
node tmp;
for (register int j = 0; j < po[i].size(); j += 2)
tmp = (tmp << (po[i][j + 1] - po[i][j])) + hash(po[i][j], po[i][j + 1] - 1);
if (S.count(tmp)) return 1;
S.insert(tmp);
}
return 0;
}
int main(){
n = read();
for (register int i = 1; i <= n; ++i){
int k = read();
for (register int j = 1; j <= k; ++j) son[i].push_back(read());
}
dfs(1);
pre(n << 1), build(a, n << 1);
int l = 0, r = n, md, ans = 0;
while (l <= r) if (check(md = (l + r) >> 1)) l = md + 1, ans = md; else r = md - 1;
printf("%d\n", ans);
}

评论