首页 > 其他分享 >P5840 [COCI2015] Divljak

P5840 [COCI2015] Divljak

时间:2023-12-25 21:56:11浏览次数:35  
标签:dep P5840 int COCI2015 tree fail Divljak now top

题意:

Alice 有 \(n\) 个字符串 \({S}_1, {S}_2, \ldots, {S}_n\),Bob 有一个字符串集合 \({T}\),一开始集合是空的。

接下来会发生 \(q\) 个操作,操作有两种形式:

  1. 1 P:Bob 往自己的集合里添加了一个字符串 \({P}\)。
  2. 2 x:Alice 询问 Bob,集合 \({T}\) 中有多少个字符串包含串 \({S}_x\)(我们称串 \({A}\) 包含串 \({B}\),当且仅当 \({B}\) 是 \({A}\) 的子串)。

分析:

需要支持询问一个模式串被多少个文本串包含,不如从操作 1 入手,由于模式串最开始就给出了,我们可以考虑计算添加一个文本串后包含了哪些模式串,给这些模式串的答案加 1 即可。

因此可以对模式串建立 AC 自动机,然后扫一遍 \(P\),并暴力跳 \(fail\),这样只能保证一个操作字典树里的每个点只经过一次,不能保证 \(P\) 只经过一次。

考虑如何优化,一个很套路的思路是连边 \(i \rightarrow fail_{i}\) 以建立一棵树,然后会产生一条从 \(P\) 中扫到的一个点在字典树上的位置到根节点的路径,扫完 \(P\) 后,把在路径并上的点的答案加 \(1\)。

现在问题转化成求路径并了,一种简单的方法是把点按照 DFS 序排序,然后将 \([h_{i+1} \rightarrow lca(h_{i},h_{i+1}) ] (i < n)\) 这条路径加 \(1\),然后 \([h_{n} \rightarrow root]\) 这条路径加 \(1\)。

可以使用差分,这样就只用树状数组了!

代码:

#include<bits/stdc++.h>
#define int long long
#define N 2000006
using namespace std;
int n, Q, tree[N][30], fail[N], cnt = 1, c[N], ans[N], vis[N], dfn[N], top[N], son[N], dep[N], siz[N], tot, father[N], P[N], h[N], len;
string s;
void insert(string s, int x) {
    int now = 1, len = s.length();
    for(int i = 0; i < len; i++) {
        if(!tree[now][s[i] - 'a']) tree[now][s[i] - 'a'] = ++cnt;
        now = tree[now][s[i] - 'a'];
    }
    vis[x] = now;
}
vector<int>p[N];
void Getfail() {
    for(int i = 0; i < 26; i++) tree[0][i] = 1;
    fail[1] = 0;
    queue<int>Q;
    Q.push(1);
    while(!Q.empty()) {
        int x = Q.front();
        Q.pop();
        for(int i = 0; i < 26; i++) {
            int y = tree[x][i];
            if(!y) { tree[x][i] = tree[fail[x]][i]; continue; }
            fail[y] = tree[fail[x]][i];
            Q.push(y);
        }
    }
    for(int i = 1; i <= cnt; i++) {
        p[i].push_back(fail[i]);
        p[fail[i]].push_back(i);
    }
}
void dfs1(int x, int fa) {
    father[x] = fa; siz[x] = 1; dfn[x] = ++tot;
    if(x != 0) dep[x] = dep[fa] + 1;
    else dep[x] = 1;
    int Maxson = -1;
    for(auto y : p[x]) {
        if(y == fa) continue;
        dfs1(y, x);
        siz[x] += siz[y];
        if(siz[y] > Maxson) {
            Maxson = siz[y];
            son[x] = y;
        }
    }
}
void dfs2(int x, int topf) {
    top[x] = topf;
    if(!son[x]) return;
    dfs2(son[x], topf);
    for(auto y : p[x]) {
        if(top[y] != -1) continue;
        dfs2(y, y);
    }
}
int lca(int x, int y) {
    while(top[x] != top[y]) {
        if(dep[top[x]] < dep[top[y]]) swap(x, y);
        x = father[top[x]];
    }
    return dep[x] < dep[y] ? x : y;
}
int lowbit(int x) { return x & (-x); }
void add(int x, int y) { for(int i = x; i <= cnt + 1; i += lowbit(i)) c[i] += y; }
int query(int x) { int res = 0; for(int i = x; i; i -= lowbit(i)) res += c[i]; return res; }
int ask(int L, int R) { return query(R) - query(L - 1); }
bool cmp(int x, int y) { return dfn[x] < dfn[y]; }
void work(string s) {
    int now = 1, L = s.length(); len = 0;
    for(int i = 0; i < L; i++) {
        now = tree[now][s[i] - 'a'];
        h[++len] = now;
    }
    sort(h + 1, h + len + 1, cmp);
    for(int i = 1; i <= len; i++) add(dfn[h[i]], 1);
    for(int i = 1; i < len; i++) add(dfn[lca(h[i], h[i + 1])], -1);
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cin >> n;
    for(int i = 1; i <= n; i++) {
        cin >> s;
        insert(s, i);
    }
    Getfail();
    memset(top, -1, sizeof(top)); dfs1(0, -1); dfs2(0, 0);
    cin >> Q;
    while(Q--) {
        int opt; cin >> opt;
        if(opt == 1) {
            string s;
            cin >> s;
            work(s);
        }
        else {
            int x; cin >> x;
            cout << ask(dfn[vis[x]], dfn[vis[x]] + siz[vis[x]] - 1) << endl;
        }
    }
    return 0;
}

标签:dep,P5840,int,COCI2015,tree,fail,Divljak,now,top
From: https://www.cnblogs.com/xcs123/p/17927052.html

相关文章

  • [COCI2015-2016#4] ENDOR 题解
    [COCI2015-2016#4]ENDOR题解首先要发现一个很重要的性质,那就是两只变色龙碰撞后回头,等效于两只变色龙继续往前走,其中向右走的颜色不变,而向左走的要改变颜色。那这样就有一种\(O(n^2)\)的做法:对于向右的变色龙,直接贡献答案;对于向左的变色龙,我们按照碰到的先后顺序枚举它前面......
  • [COCI2015-2016#7] Prokletnik
    [COCI2015-2016#7]Prokletnik有那么一点点启发性。假设右端点是最大值,思路很简单很经典,考虑扫描线+线段树,那么修改涉及到的点就是当前的后缀最小值,维护一个单调不减的单调栈,那么单调栈里面的点都要改。难道我们要遍历单调栈吗?哈哈,并不用,我们直接在单调栈上面建一棵线段树就行......
  • [百紫祭] 洛谷P5840做题笔记
    [百紫祭]洛谷P5840做题笔记luogu传送门前置芝士:AC自动机,树上差分,树剖求LCA,树状数组。前言一篇笔记需要一张头图。题意给出\(n\)个字符串\(S_1,S_2\.\.\.\S_n\)和一个初始为空的字符串集合\(T\),接下来\(q\)次操作。操作分为修改和询问。修改操作为向\(T\)中......
  • [COCI2015-2016#1] RELATIVNOST
    RELATIVNOSTの传送门线段树优化dp已经有很多题解讲的很好了。dp状态是一样的,但是一般的线段树优化dp空间要开$4n$,而且只利用到线段树的一点点功能(单点修改),所以可以先优化空间,从$4n$优化到$2n$。如下图所示。如果用线段树优化dp的方法会导致树不止$\logn+1$层......
  • P7862 [COCI2015-2016#2] DRŽAVA 题解
    适当进行骗分是真的有用。\(40pts\):对于每两个点建立一条边,然后在贪心每次求最小边,在期间进行01背包即可,01背包用于处理模数。设\(dp_{i,j}\)代表以\(i\)为编号的一......