题意
题解
将文件系统建为 T r i e Trie Trie,设根节点深度为 0 0 0,此时有 ∑ u ∈ T r i e u ( d e p t h [ u ] + 1 ) ≤ 2 × 1 0 5 \sum_{u\in Trie}u(depth[u]+1)\leq 2\times 10^5 ∑u∈Trieu(depth[u]+1)≤2×105。
考虑将子文件夹,即某棵子树序列化。具体而言,后序遍历
T
r
i
e
Trie
Trie,设以
u
u
u 为根节点的子树,按照与孩子节点
c
h
ch
ch 连边所代表的字符串
s
c
h
s_{ch}
sch 使孩子节点有序,那么可以自定义序列化为
s
e
r
i
a
l
(
u
)
=
(
s
c
h
0
,
s
e
r
i
a
l
(
c
h
0
)
)
(
s
c
h
1
,
s
e
r
i
a
l
(
c
h
1
)
)
⋯
)
serial(u)=(s_{ch_0},serial(ch_0))(s_{ch_1},serial(ch_1))\cdots)
serial(u)=(sch0,serial(ch0))(sch1,serial(ch1))⋯) 序列化的子树中包含了子树的信息,可以通过反序列化得到原来的子树。序列化类似于哈希的思想,且保证哈希值对于不同结构子树不会产生冲突。
任一节点仅会出现在其本身与其祖先节点的序列化表示中,那么所有节点的序列化表示规模为 ∑ u ∈ T r i e u ( d e p t h [ u ] + 1 ) ≤ 2 × 1 0 5 \sum_{u\in Trie}u(depth[u]+1)\leq 2\times 10^5 ∑u∈Trieu(depth[u]+1)≤2×105。统计序列化表示出现的次数,通过遍历 T r i e Trie Trie 统计答案即可。
class Solution
{
static const int maxn = 200005;
public:
int rt, tot;
map<string, int> trie[maxn];
string rec[maxn];
unordered_map<string, int> cnt;
void insert(vector<string> &vec)
{
int p = rt;
for (auto &u : vec)
{
if (trie[p].count(u) == 0)
trie[p][u] = tot++;
p = trie[p][u];
}
}
string get_serial(int p)
{
string res = "";
for (auto &pr : trie[p])
res += '(' + pr.first + ',' + get_serial(pr.second) + ')';
++cnt[res];
return rec[p] = res;
}
void dfs(int p, vector<string> &tmp, vector<vector<string>> &res)
{
if (cnt[rec[p]] > 1 && rec[p].size() > 0)
return;
if (p != rt)
res.push_back(tmp);
for (auto &pr : trie[p])
{
tmp.push_back(pr.first);
dfs(pr.second, tmp, res);
tmp.pop_back();
}
}
vector<vector<string>> deleteDuplicateFolder(vector<vector<string>> &paths)
{
tot = 0, rt = tot++;
for (auto &v : paths)
insert(v);
get_serial(rt);
vector<vector<string>> res;
vector<string> tmp;
dfs(rt, tmp, res);
return res;
}
};