26HDU07A 今晚吃什么 题解 AC自动机+虚树差分

原题链接

首先一个朴素的想法肯定是定义 \(f_{i,j}\) 表示说现在是第 \(i\) 条评论,然后目前的真评论总长度是 \(j\) 的方案数。那么转移方程也是比较好写的:

\(\begin{aligned}f_{i,j}=\sum_{k}f_{k,j-1}(S_k\ 是\ S_i\ 的子串)\end{aligned}\)

我们思考一下这个东西怎么优化。

Hint 1

第二维不会太大。

一个字符串必定比其子串长至少 \(1\)。再加上输入的字符串长度单调不减,那么第二维应该是 \(\sqrt{L}\) 级别的。

子串,多模式串,考虑建立 AC 自动机,那么上面那个东西就相当于第 \(i\) 个评论所走过的点,这些点在 fail 树上到根的路径的 \(f_{v,j-1}\) 的总和。但是每个点都跑一边所有 fail 树,那么时间复杂度显然是不支持的。我们就想到预处理一下 \(g_{v,i}\) 表示从 \(v\) 节点开始,走到 fail 树根路径上的所有 \(f_{u,i}\) 的和。那么我们就想到这个东西应该以 fail 树上的拓扑序来弄,用 dfn 序也一样。但是一个东西 \(f_{u,i}\) 并不是 trie 树上从根到这个评论所代表的节点路上所有节点的 \(g\) 之和,我们可以想到,如果我这个 fail 树指向了原串的祖先,那么会有重复计数。

这是虚树差分的一个 trick。我们将 trie 路径上的所有节点按照 fail 树 dfn 排序,我们只要减掉相邻的点的 LCA 就行了。这也比较好理解。

#include <bits/stdc++.h>
#define debug puts("Wait for me.");
template <typename T>void read(T& t){t=0; char ch=getchar(); int fflag=1;while(!('0'<=ch&&ch<='9')){if(ch=='-') fflag=-1;ch=getchar();}while(('0'<=ch&&ch<='9')){t=t*10+ch-'0'; ch=getchar();} t*=fflag;}
template <typename T,typename... Args>void read(T& t, Args&... args){read(t);read(args...);}
template <typename T>void write(const T &x) {if(x < 10) {putchar(x + '0');return;}write(x / 10);putchar(x % 10 + '0');}
#define pb push_back
using namespace std;
#define rep(I,J,K) for(int I=(int)J;I<=(int)K;++I)
#define per(I,J,K) for(int I=(int)J;I>=(int)K;--I)
typedef long long ll;typedef double db;typedef pair<int,int>pii;
mt19937 mrand(random_device{}());
int rnd(int x){return mrand()%x;}
// head

const int N = 1e5 + 10, S = 2e5 + 10, SQ = 650,inf = 0x3f3f3f3f, P = 998244353;

int T;

int n;
void add(int &x, int y) {x += y; if(x >= P) x -= P;}
void del(int &x, int y) {x -= y; if(x < 0) x += P;}
struct AC_automaton {
    struct Node {
        int son[26], fail;
    }tr[S];
    int cnt;
    vector<int>id[N];
    int _end[S], end[N], fa[S][19], dep[S], dfn[S], tot, ans[N], f[N], pre[S], _dfn[S];
    vector<int>G[S], lca[S];
    void init() {
        rep(i, 0, cnt) {
            memset(tr[i].son, 0, sizeof tr[i].son);
            tr[i].fail = 0;
        } 
        rep(i, 1, n) end[i] = ans[i] = f[i] = 0;
        rep(i, 0, cnt) _end[i] = 0, G[i].clear(), dep[i] = 0, pre[i] = 0;
        rep(i, 0, cnt) rep(j, 0, 18) fa[i][j] = 0;
        cnt = tot = 0;
    }
    void insert(string st, int idx) {
        int cur = 0; id[idx].clear();
        rep(i, 0, st.size() - 1) {
            cur = (tr[cur].son[st[i] - 'a']) ? tr[cur].son[st[i] - 'a'] : (tr[cur].son[st[i] - 'a'] = ++cnt);
            id[idx].pb(cur);
        }
        _end[end[idx] = cur] = idx;
        return;
    }
    void build() {
        queue<int>Q;
        rep(i, 0, 25) if(tr[0].son[i]) Q.push(tr[0].son[i]);
        while(!Q.empty()) {
            int u = Q.front(); Q.pop();
            rep(i, 0, 25) {
                if(tr[u].son[i]) {
                    tr[tr[u].son[i]].fail = tr[tr[u].fail].son[i];
                    Q.push(tr[u].son[i]);
                } else {
                    tr[u].son[i] = tr[tr[u].fail].son[i];
                }
            }
        }
    }
    void dfs(int u) {
        _dfn[dfn[u] = ++tot] = u;
        for(int v : G[u]) {
            dep[v] = dep[u] + 1;
            fa[v][0] = u;
            dfs(v);
        }
    }
    void ST() {
        rep(i, 1, 18)
            rep(j, 1, cnt)
                fa[j][i] = fa[fa[j][i - 1]][i - 1];
    }
    int LCA(int x, int y) {
        if(dep[x] < dep[y]) swap(x, y);
        rep(i, 0, 18) if((dep[x] - dep[y] >> i) & 1) x = fa[x][i];
        per(i, 18, 0) if(fa[x][i] ^ fa[y][i]) x = fa[x][i], y = fa[y][i];
        return (x == y) ? x : fa[x][0];
    }
    void Do() {
        rep(i, 1, cnt) G[tr[i].fail].pb(i);
        dfs(0); ST();
        rep(i, 1, n) {
            sort(id[i].begin(), id[i].end(), [&](int x, int y){return dfn[x] < dfn[y];});
            lca[i].clear();
            rep(j, 1, id[i].size() - 1) lca[i].pb(LCA(id[i][j - 1], id[i][j]));
        }
        ans[1] = n;
        rep(i, 1, n) f[i] = 1;
        rep(i, 2, min(n, SQ)) {
            rep(j, 1, tot) {
                // 按照 fail 树 dfs 序来弄
                pre[_dfn[j]] = 0;
                add(pre[_dfn[j]], pre[tr[_dfn[j]].fail]);
                if(_end[_dfn[j]]) add(pre[_dfn[j]], f[_end[_dfn[j]]]);
            }
            rep(j, 1, n) {
                // 计算 f_i
                int res = 0;
                for(int u : id[j]) add(res, pre[u]);
                for(int u : lca[j]) del(res, pre[u]);
                del(res, f[j]);
                f[j] = res;
            }
            rep(j, 1, n) add(ans[i], f[j]);
        }
        rep(i, 0, n - 1) cout << ans[n - i] << ' ';
        cout << endl;
    }
}ACAM;

void work() {
    cin >> n;
    ACAM.init();
    rep(i, 1, n) {
        string st;
        cin >> st;
        ACAM.insert(st, i);
    }
    ACAM.build();
    ACAM.Do();
    return;
}

int main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> T;
    while(T--) work();
    return 0;
}
posted @ 2026-08-22 19:02  Mercury_City  阅读(4)  评论(0)    收藏  举报