1. 程式人生 > 實用技巧 >「SDOI2016」生成魔咒

「SDOI2016」生成魔咒

知識點: SAM

原題面 Loj Luogu


分析題意

\(S\) 的所有字首的本質不同的子串的個數。

考察對 SAM 構建過程的理解。

對於一個確定的字串 \(S\),其本質不同子串的個數,等於所有狀態所表示子串的個數之和。
即有下式:

\[ans = \sum_{u\in \operatorname{DAWG}}{\operatorname{len(u)} - \operatorname{len(\operatorname{link}(u))}} \]

對於字串 \(S\),考慮新加入字元 \(c\) 的影響。
加入 \(c\) 後,顯然答案增加 不在 \(S\) 中出現的 \(S+c\)

字尾的個數
設表示 \(S+c\) 的狀態為 \(a\),考慮第一個在 \(S\) 中出現的 \(S+c\) 的字尾,會在 SAM 構建中賦值給 \(\operatorname{link}(a)\) 上。
則新字元的貢獻即為 \(\operatorname{len}(a) - \operatorname{len}(\operatorname{link}(a))\)

感覺在 SDOI 見了不少模板題了,傳統藝能?


程式碼實現

//知識點:SAM
/*
By:Luckyblock
*/
#include <map>
#include <cstdio>
#include <ctype.h>
#include <cstring>
#include <algorithm>
#define ll long long
const int kMaxn = 1e5 + 10;
//=============================================================
int n, last = 1, node_num = 1, link[kMaxn << 1];
std :: map <int, int> ch[kMaxn << 1]; 
ll ans, len[kMaxn << 1];
//=============================================================
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 GetMax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
void GetMin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
int Insert(int c_) {
  int p = last, now = last = ++ node_num;
  len[now] = len[p] + 1ll;
  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] + 1ll) {link[now] = q; return now;}
  int newq = ++ node_num;
  ch[newq] = ch[q];
  link[newq] = link[q], len[newq] = len[p] + 1ll;
  link[q] = link[now] = newq;
  for (; p && ch[p][c_] == q; p = link[p]) ch[p][c_] = newq;
  return now;
}
//=============================================================
int main() {
  int n = read();
  for (int i = 1; i <= n; ++ i) {
    int x = read(), now = Insert(x);
    printf("%lld\n", ans += (len[now] - len[link[now]]));
  }
  return 0;
}