「ZJOI2015」诸神眷顾的幻想乡

知识点: 广义 SAM,暴力

原题面 Loj Luogu


yuka

萌妹子幽香有多萌


题意简述

给定一棵 \(n\) 个节点的树,每个节点都有一个字符 \(c_i\)
可任意选择两个点 \(u,v\),路径 \(u\rightarrow v\) 上的字符构成一个字符串(\(v\rightarrow u\) 构成的字符串可能不同)。
求所有可以构成的 不同的字符串的数量。
\(1\le n\le 10^5, 1\le c_i\le 10\)最多存在 \(20\) 个叶节点


分析题意

发现 任意 选择两个节点构成的所有字符串,等价于选择两个 叶节点 构成的字符串的 所有子串

如果给定的是一条链,就非常好做了。
直接将整个串正反插入广义 SAM 中,求 \(\sum\operatorname{len}(i) - \operatorname{len}(\operatorname{link}(i))\) 即可。
但是给不得,只能来猜结论了。

考虑怎么把弯曲的路径掰♂直。
发现 任何一条路径,都会在叶节点到其他叶节点 构成的路径中出现。

题中给了一个很特别的性质:最多存在 \(20\) 个叶节点
考虑暴力枚举选择两个 叶节点 构成的字符串,并将它们插入广义 SAM 中,求 \(\sum\operatorname{len}(i) - \operatorname{len}(\operatorname{link}(i))\) 即为答案。

复杂度大概是 \(O(\text{leaves_num}\times n)\),可过。


代码实现

//知识点:广义 SAM,暴力
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define ll long long
const int kMaxn = 1e5 + 10;
const int kMaxm = 10;
//=============================================================
ll ans;
char S[kMaxn];
bool is_leaves[kMaxn];
int leaves_num, leaves[50];
int edge_num, c[kMaxn], into[kMaxn], head[kMaxn], v[kMaxn << 1], ne[kMaxn << 1];
int node_num = 1, ch[kMaxn << 5][kMaxm], len[kMaxn << 5], link[kMaxn << 5];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void AddEdge(int u_, int v_) {
  v[++ edge_num] = v_, ne[edge_num] = head[u_], head[u_] = edge_num;
}
int Insert(int c_, int last_) {
  if (ch[last_][c_]) {
    int p = last_, q = ch[p][c_];
    if (len[p] + 1 == len[q]) return q;
    int newq = ++ node_num;
    memcpy(ch[newq], ch[q], sizeof(ch[q])); 
    len[newq] = len[p] + 1; 
    link[newq] = link[q];
    link[q] = newq; 
    for (; p && ch[p][c_] == q; p = link[p]) ch[p][c_] = newq;
    return newq;
  }
  int p = last_, now = ++ node_num;
  len[now] = len[p] + 1;
  for (; p && ! ch[p][c_]; p = link[p]) ch[p][c_] = now;
  if (! p) {link[now] = 1; return now;} 
  int q = ch[p][c_];
  if (len[q] == len[p] + 1) {link[now] = q; return now;}
  int newq = ++ node_num;
  memcpy(ch[newq], ch[q], sizeof(ch[q])); 
  link[newq] = link[q], len[newq] = len[p] + 1; 
  link[q] = link[now] = newq;
  for (; p && ch[p][c_] == q; p = link[p]) ch[p][c_] = newq;
  return now;
}
void Dfs(int u_, int fa_, int dep_) {
  S[dep_] = c[u_];
  if (is_leaves[u_] && dep_ != 1) {
    int last = 1;
    for (int i = 1; i <= dep_; ++ i) last = Insert(S[i], last);
    return ;
  }
  for (int i = head[u_]; i; i = ne[i]) {
    if (v[i] != fa_) Dfs(v[i], u_, dep_ + 1);
  }
}
//=============================================================
int main() {
  int n = read(), Marisa = read();
  for (int i = 1; i <= n; ++ i) c[i] = read();
  for (int i = 1; i < n; ++ i) {
    int u = read(), v = read(); 
    AddEdge(u, v), AddEdge(v, u);
    into[u] ++, into[v] ++;
  }
  for (int i = 1; i <= n; ++ i) {
    if (into[i] == 1) {
      leaves[++ leaves_num] = i;
      is_leaves[i] = true; 
    }
  }
  for (int i = 1; i <= leaves_num; ++ i) {
    Dfs(leaves[i], 0, 1); 
  }
  for (int i = 2; i <= node_num; ++ i) {
    ans += len[i] - len[link[i]]; 
  }
  printf("%lld\n", ans);
  return 0; 
}
posted @ 2020-08-20 15:55  Luckyblock  阅读(152)  评论(0编辑  收藏  举报