1. 程式人生 > 實用技巧 >「ZJOI2015」諸神眷顧的幻想鄉

「ZJOI2015」諸神眷顧的幻想鄉

知識點: 廣義 SAM,暴力

原題面 Loj Luogu


萌妹子幽香有多萌


題意簡述

給定一棵 \(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; 
}