字符串做题笔记
字符串做题笔记
会持续跟新吗(恼……
目录
CF710F - String Set Queries
注意到所有串的长度只有 \(\sqrt{n}\) 种,所以直接把所有串的哈希插入 map 里,然后直接按照有效串长查询即可。
时间复杂度 \(O(n \sqrt{n} \log n)\)。
ケロシの代码
const int N = 3e5 + 5;
const int V = 3e5;
const int P1 = 998244353;
const int P2 = 1e9 + 7;
int add(int x, int y, int p) { return (x + y < p ? x + y : x + y - p); }
void Add(int & x, int y, int p) { x = (x + y < p ? x + y : x + y - p); }
int sub(int x, int y, int p) { return (x < y ? x - y + p : x - y); }
void Sub(int & x, int y, int p) { x = (x < y ? x - y + p : x - y); }
int mul(int x, int y, int p) { return (1ll * x * y) % p; }
void Mul(int & x, int y, int p) { x = (1ll * x * y) % p; }
mt19937 rnd(time(0));
bool ip(int x) {
for(int i = 2; i * i <= x; i ++)
if(x % i == 0) return 0;
return 1;
}
struct Hash {
int x, y;
bool operator == (const Hash & A) const {
return x == A.x && y == A.y;
}
bool operator < (const Hash & A) const {
if(x == A.x) return y < A.y;
return x < A.x;
}
Hash operator + (const int & A) {
return {add(x, A, P1), add(y, A, P2)};
}
Hash operator - (const int & A) {
return {sub(x, A, P1), sub(y, A, P2)};
}
Hash operator * (const int & A) {
return {mul(x, A, P1), mul(y, A, P2)};
}
Hash & operator += (const int & A) {
*this = * this + A;
return *this;
}
Hash & operator -= (const int & A) {
*this = * this - A;
return *this;
}
Hash & operator *= (const int & A) {
*this = * this * A;
return *this;
}
Hash operator + (const Hash & A) {
return {add(x, A.x, P1), add(y, A.y, P2)};
}
Hash operator - (const Hash & A) {
return {sub(x, A.x, P1), sub(y, A.y, P2)};
}
Hash operator * (const Hash & A) {
return {mul(x, A.x, P1), mul(y, A.y, P2)};
}
Hash & operator += (const Hash & A) {
*this = * this + A;
return *this;
}
Hash & operator -= (const Hash & A) {
*this = * this - A;
return *this;
}
Hash & operator *= (const Hash & A) {
*this = * this * A;
return *this;
}
};
int q, B;
int c[N];
Hash pw[N], h[N];
map<Hash, int> mp;
void solve() {
B = rnd() % int(1e6) + 1145;
while(! ip(B)) B ++;
cin >> q;
pw[0] = {1, 1};
FOR(i, 1, V) pw[i] = pw[i - 1] * B;
REP(_, q) {
int o; string s;
cin >> o >> s;
int n = SZ(s); s = ' ' + s;
FOR(i, 1, n) h[i] = h[i - 1] + pw[i] * s[i];
if(o == 1) {
c[n] ++;
mp[h[n] * pw[V - 1]] ++;
}
if(o == 2) {
c[n] --;
mp[h[n] * pw[V - 1]] --;
}
if(o == 3) {
ll ans = 0;
FOR(len, 1, n) if(c[len]) FOR(l, 1, n - len + 1) {
int r = l + len - 1;
ans += mp[(h[r] - h[l - 1]) * pw[V - l]];
}
cout << ans << endl << flush;
}
}
}
CF1780G - Delicious Dessert
建出 SAM,通过在 link tree 上 dp 算出每个等价类的出现次数 \(f_u\),若等价类中字串长度为 \([l,r]\),那么贡献为 \([l,r]\) 中为 \(f_u\) 约数的个数。
不难想到预处理每个数的约数,然后查询的时候直接二分找 \([l,r]\) 中有多少约数即可。
时间复杂度 \(O(n \log n)\)。
ケロシの代码
const int N = 2e6 + 5;
int n;
string s;
struct Node {
int len, link;
int nxt[26];
} st[N];
int lst, sz;
vector<int> e[N], d[N >> 1];
int f[N];
ll ans;
void init() {
st[1].link = - 1;
st[1].len = 0;
lst = sz = 1;
}
void extend(int c) {
int cur = ++ sz;
st[cur].len = st[lst].len + 1;
f[cur] = 1;
int p = lst;
while(p != - 1 && ! st[p].nxt[c]) {
st[p].nxt[c] = cur;
p = st[p].link;
}
if(p == - 1) {
st[cur].link = 1;
}
else {
int q = st[p].nxt[c];
if(st[p].len + 1 == st[q].len) {
st[cur].link = q;
}
else {
int cln = ++ sz;
st[cln].len = st[p].len + 1;
REP(i, 26) st[cln].nxt[i] = st[q].nxt[i];
st[cln].link = st[q].link;
while(p != - 1 && st[p].nxt[c] == q) {
st[p].nxt[c] = cln;
p = st[p].link;
}
st[cur].link = st[q].link = cln;
}
}
lst = cur;
}
int F(int r, int x) {
return lower_bound(ALL(d[x]), r + 1) - BG(d[x]);
}
int F(int l, int r, int x) {
return F(r, x) - F(l - 1, x);
}
void dfs(int u) {
for(int v : e[u]) {
dfs(v);
f[u] += f[v];
}
if(u > 1) ans += 1ll * F(st[st[u].link].len + 1, st[u].len, f[u]) * f[u];
}
void solve() {
cin >> n;
cin >> s; s = ' ' + s;
init();
FOR(i, 1, n) extend(s[i] - 'a');
FOR(i, 2, sz) e[st[i].link].push_back(i);
FOR(i, 1, n) for(int j = i; j <= n; j += i)
d[j].push_back(i);
dfs(1);
cout << ans << endl;
}
CF452E - Three strings
考虑将三个串建出一个广义 SAM,然后把每个等价类在三个串的出现次数乘起来,做一次区间加即可。
时间复杂度 \(O(n)\)。
ケロシの代码
const int N = 6e5 + 5;
const int P = 1e9 + 7;
const int INF = 1e9 + 7;
inline int add(int x, int y) { return (x + y < P ? x + y : x + y - P); }
inline void Add(int & x, int y) { x = (x + y < P ? x + y : x + y - P); }
inline int sub(int x, int y) { return (x < y ? x - y + P : x - y); }
inline void Sub(int & x, int y) { x = (x < y ? x - y + P : x - y); }
inline int mul(int x, int y) { return (1ll * x * y) % P; }
inline void Mul(int & x, int y) { x = (1ll * x * y) % P; }
inline int mul(initializer_list<int> a) {
int res = 1;
for(int x : a) Mul(res, x);
return res;
}
int a[3];
string s[3];
int tr[N][26], idx;
int pre[N], id[N], fa[N];
int fi[N], ne[N], to[N], ecnt;
int f[N][3], c[N];
void add_edge(int u, int v) {
ne[++ ecnt] = fi[u];
to[ecnt] = v;
fi[u] = ecnt;
}
struct Node {
int len, link;
map<int, int> nxt;
} st[N];
int sz;
int insert(int x, int lst) {
int cur = ++ sz;
st[cur].len = st[lst].len + 1;
int p = lst;
while(p != - 1 && ! st[p].nxt.count(x)) {
st[p].nxt[x] = cur;
p = st[p].link;
}
if(p == - 1) {
st[cur].link = 1;
}
else {
int q = st[p].nxt[x];
if(st[p].len + 1 == st[q].len) {
st[cur].link = q;
}
else {
int cln = ++ sz;
st[cln].len = st[p].len + 1;
st[cln].nxt = st[q].nxt;
st[cln].link = st[q].link;
while(p != - 1 && st[p].nxt[x] == q) {
st[p].nxt[x] = cln;
p = st[p].link;
}
st[cur].link = st[q].link = cln;
}
}
return cur;
}
void build() {
st[1].link = - 1;
id[0] = sz = 1;
queue<int> q;
REP(i, 26) if(tr[0][i]) q.push(tr[0][i]);
while(! q.empty()) {
int u = q.front();
q.pop();
id[u] = insert(pre[u], id[fa[u]]);
REP(i, 26) if(tr[u][i]) q.push(tr[u][i]);
}
FOR(i, 2, sz) add_edge(st[i].link, i);
}
void dfs(int u) {
for(int i = fi[u]; i; i = ne[i]) {
int v = to[i];
dfs(v);
REP(i, 3) f[u][i] += f[v][i];
}
}
void solve() {
int len = INF;
REP(i, 3) {
cin >> s[i];
a[i] = SZ(s[i]); s[i] = ' ' + s[i];
chmin(len, a[i]);
int u = 0;
FOR(j, 1, a[i]) {
if(! tr[u][s[i][j] - 'a']) {
tr[u][s[i][j] - 'a'] = ++ idx;
fa[idx] = u;
pre[idx] = s[i][j] - 'a';
}
u = tr[u][s[i][j] - 'a'];
}
}
build();
REP(i, 3) {
int u = 1;
FOR(j, 1, a[i]) {
u = st[u].nxt[s[i][j] - 'a'];
f[u][i] ++;
}
}
dfs(1);
FOR(u, 2, sz) {
int val = mul({f[u][0], f[u][1], f[u][2]});
Add(c[st[st[u].link].len + 1], val);
Sub(c[st[u].len + 1], val);
}
FOR(i, 1, len) Add(c[i], c[i - 1]);
FOR(i, 1, len) cout << c[i] << " "; cout << endl;
}
CF204E - Little Elephant and Strings
首先将所有的字符串建出广义 SAM,然后考虑 SAM 上一个节点在多少个字符串里出现过。
不难发现每个字符串在 link tree 上的贡献是每个前缀对应节点建出的一颗类似虚树的东西,虚树上每个点都增加 \(1\) 的贡献。
考虑直接差分,将所有前缀对应节点都加一,将 dfn 序相邻的两个的 LCA 减一,算子树和即可。
接下来考虑如何计算答案,考虑对于每个右端点,左端点对应子串的出现次数的单调的,所以直接在 link tree 上倍增即可。
时间复杂度 \(O(n \log n)\)。
ケロシの代码
const int N = 2e5 + 5;
int n, k, a[N];
string s[N];
int tr[N][26], idx;
vector<int> e[N];
int pre[N], id[N], fa[N];
int fi[N], ne[N], to[N], ecnt;
int dfn[N], cnt, d[N], f[N][19], F[N];
void add(int u, int v) {
ne[++ ecnt] = fi[u];
to[ecnt] = v;
fi[u] = ecnt;
}
struct Node {
int len, link;
map<int, int> nxt;
} st[N];
int sz;
int insert(int x, int lst) {
int cur = ++ sz;
st[cur].len = st[lst].len + 1;
int p = lst;
while(p != - 1 && ! st[p].nxt.count(x)) {
st[p].nxt[x] = cur;
p = st[p].link;
}
if(p == - 1) {
st[cur].link = 1;
}
else {
int q = st[p].nxt[x];
if(st[p].len + 1 == st[q].len) {
st[cur].link = q;
}
else {
int cln = ++ sz;
st[cln].len = st[p].len + 1;
st[cln].nxt = st[q].nxt;
st[cln].link = st[q].link;
while(p != - 1 && st[p].nxt[x] == q) {
st[p].nxt[x] = cln;
p = st[p].link;
}
st[cur].link = st[q].link = cln;
}
}
return cur;
}
void build() {
st[1].link = - 1;
id[0] = sz = 1;
queue<int> q;
REP(i, 26) if(tr[0][i]) q.push(tr[0][i]);
while(! q.empty()) {
int u = q.front();
q.pop();
id[u] = insert(pre[u], id[fa[u]]);
REP(i, 26) if(tr[u][i]) q.push(tr[u][i]);
}
FOR(i, 2, sz) add(st[i].link, i);
}
void dfs0(int u) {
dfn[u] = ++ cnt;
FOR(i, 1, 18) f[u][i] = f[f[u][i - 1]][i - 1];
for(int i = fi[u]; i; i = ne[i]) {
int v = to[i];
d[v] = d[u] + 1;
f[v][0] = u;
dfs0(v);
}
}
int lca(int u, int v) {
if(d[u] < d[v]) swap(u, v);
ROF(i, 18, 0) if(d[f[u][i]] >= d[v])
u = f[u][i];
if(u == v) return u;
ROF(i, 18, 0) if(f[u][i] != f[v][i])
u = f[u][i], v = f[v][i];
return f[u][0];
}
void dfs1(int u) {
for(int i = fi[u]; i; i = ne[i]) {
int v = to[i];
dfs1(v);
F[u] += F[v];
}
}
void solve() {
cin >> n >> k;
FOR(i, 1, n) {
cin >> s[i];
a[i] = SZ(s[i]); s[i] = ' ' + s[i];
int u = 0;
FOR(j, 1, a[i]) {
if(! tr[u][s[i][j] - 'a']) {
tr[u][s[i][j] - 'a'] = ++ idx;
fa[idx] = u;
pre[idx] = s[i][j] - 'a';
}
u = tr[u][s[i][j] - 'a'];
}
}
build();
FOR(i, 1, n) {
int u = 1;
e[i].push_back(u);
FOR(j, 1, a[i]) {
u = st[u].nxt[s[i][j] - 'a'];
e[i].push_back(u);
}
}
d[1] = 1; dfs0(1);
FOR(i, 1, n) {
sort(ALL(e[i]), [&] (int x, int y) {
return dfn[x] < dfn[y];
});
FOR(j, 0, a[i]) F[e[i][j]] ++;
FOR(j, 1, a[i]) F[lca(e[i][j - 1], e[i][j])] --;
}
dfs1(1);
F[0] = n;
FOR(i, 1, n) {
ll ans = 0;
FOR(j, 1, a[i]) {
int u = e[i][j];
ROF(l, 18, 0) if(F[f[u][l]] < k)
u = f[u][l];
if(F[u] < k) u = f[u][0];
ans += st[u].len;
}
cout << ans << " ";
}
cout << endl;
}

浙公网安备 33010602011771号