喵星球上的點名(後綴自動機+dfs序+莫隊)

喵星球上的點名

一道據傳言有多種解法的題(不過大多是因爲數據太弱過的)。先用AC自動機搞了一上午,無果;看了題解,後綴自動機+莫隊?正好是我最喜歡的算法之一+正在學習的算法,就這個了!然後由於廣義自動機的lastlast標記有個地方忘了初始化。。。然後在大物課上調了三節課,洛谷一頁都是我,hhh

題意

NN個同學,名字包含姓和名;然後老師要點MM次名,某次點名若是某個同學的姓或者名的子串,則這位同學要答到。求:

  1. 每次點名會有多少同學答到
  2. MM次點名後每位同學分別會答到多少次

思路後綴自動機+dfsdfs序+莫隊

  1. 由於涉及多字符串,當然是用廣義後綴自動機搞呀(也可以在字符串之間插入不涉及的數字,並且用mapmap存轉移邊);同時記得在建後綴自動機時給每個節點表明是屬於哪個人的,畢竟後面要分別統計每個人的答到次數;還有虛點的問題,這裏的處理其實比較隨意,你既可以當做是當前同學的,也可以當做是之前同學的,甚至可以當做一個不會出現的同學的(比如默認爲00),這點是非常有趣的。
  2. 詢問中需要知道每次點名會有多少人答到,顯然可以直接在後綴自動機上跑這個點名的字符串,找到那個節點後,屬於這個節點的整棵parentparent treetree子樹的同學都是要答到的。
  3. 這時我們容易想到處理出parentparent treetreedfsdfs序,這樣屬於某個節點的整棵子樹在dfsdfs序上都是連續的一段,當我們想要知道里面有多少個不同的同學的姓名時,也就是詢問區間不同種類數字個數!就可以直接莫隊處理啦(也可以像HH的項鍊那樣用樹狀數組處理啦)。
  4. 至此,第一個問題就輕鬆解決了,那麼第二問呢?第二個問題可以等效爲某個節點被多少詢問區間所包含!注意到在使用莫隊的過程中,每個節點總是在不斷地進入,出去。那麼,在進入之後,出來之前發生了什麼呢?顯然這個節點(同學)在不斷地貢獻答案,也就是他在這段時間內一直被包含!因此,在端點移動過程中,每次有新的未出現的節點進入區間,就記錄這個節點的進入時間,當他出去的時候,就統計答案。這樣,每個同學被點到的次數就被分成了多段連續的時間,加在一起就是總的次數!
  5. 補充:第二問同樣可以用樹狀數組處理:將區間放在樹狀數組上,每當到達區間左端點時,bit[L]+1bit[L]+1,到達區間右端點時,bit[L]1bit[L]-1;沒當到達某個人的姓名時(兩部分),對於每個人的兩個字符串,靠左邊的統計答案爲ans=qsum(pos1)ans=qsum(pos1),靠右邊的統計爲ans+=qsum(pos2)qsum(pos1)ans+=qsum(pos2)-qsum(pos1);這樣的處理就可以知道這個人總共被覆蓋多少次啦!
  6. 妙哉!!!

代碼

#include "bits/stdc++.h"
#define hhh printf("hhh\n")
#define see(x) (cerr<<(#x)<<'='<<(x)<<endl)
using namespace std;
typedef long long ll;
typedef pair<int,int> pr;
inline int read() {int x=0;char c=getchar();while(c<'0'||c>'9')c=getchar();while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();return x;}

const int maxn = 4e5+10;
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;
const double eps = 1e-7;

int N, M;
map<int,int> ch[maxn];
int len[maxn], fa[maxn], belong[maxn], last=1, sz=1;
int head[maxn], to[maxn], nxt[maxn], tot;
int siz[maxn], id[maxn], rk[maxn], dfn, vis[maxn], pre[maxn];
int cur, cntq, ans1[maxn], ans2[maxn], block;

inline void add_edge(int u, int v) {
    ++tot; to[tot]=v; nxt[tot]=head[u]; head[u]=tot;
}

struct Q{
    int l, r, id;
    friend bool operator < (const Q &a, const Q &b) {
        if((a.l-1)/block!=(b.l-1)/block) return a.l<b.l;
        if((a.l-1)/block%2) return a.r>b.r;
        return a.r<b.r;
    }
}q[maxn];

void add(int c, int ID) {
    int p=last, np=last=++sz;
    len[np]=len[p]+1, belong[np]=ID;
    for(; p&&!ch[p].count(c); p=fa[p]) ch[p][c]=np;
    if(!p) fa[np]=1;
    else {
        int q=ch[p][c];
        if(len[q]==len[p]+1) fa[np]=q;
        else {
            int nq=++sz; len[nq]=len[p]+1;
            fa[nq]=fa[q]; fa[np]=fa[q]=nq;
            ch[nq]=ch[q];
            for(; p&&ch[p][c]==q; p=fa[p]) ch[p][c]=nq;
        }
    }
}

void dfs(int u) {
    rk[id[u]=++dfn]=u; siz[u]=1;
    for(int i=head[u]; i; i=nxt[i]) {
        dfs(to[i]); siz[u]+=siz[to[i]];
    }
}

int main() {
    //ios::sync_with_stdio(false); cin.tie(0);
    N=read(), M=read();
    for(int i=1, L; i<=N; ++i) {
        last=1; L=read(); for(int j=1; j<=L; ++j) add(read(),i);
        last=1; L=read(); for(int j=1; j<=L; ++j) add(read(),i);
    }
    for(int i=2; i<=sz; ++i) add_edge(fa[i],i); dfs(1);
    for(int i=1; i<=M; ++i) {
        int L=read(), now=1;
        for(int j=1; j<=L; ++j) now=ch[now][read()];
        if(now) q[++cntq]=(Q){id[now],id[now]+siz[now]-1,i};
    }
    block=ceil(sqrt(dfn));
    sort(q+1,q+1+cntq);
    int l=1, r=0; //不用把第一個區間單獨處理,莫隊其實會自動處理好的,真妙!
    for(int i=1; i<=cntq; ++i) {
        while(l<q[i].l) {
            int p=belong[rk[l]];
            vis[p]--;
            if(!vis[p]&&p) cur--, ans2[p]+=i-pre[p];
            l++;
        }
        while(l>q[i].l) {
            --l;
            int p=belong[rk[l]];
            if(!vis[p]&&p) cur++, pre[p]=i;
            vis[p]++;
        }
        while(r<q[i].r) {
            ++r;
            int p=belong[rk[r]];
            if(!vis[p]&&p) cur++, pre[p]=i;
            vis[p]++;
        }
        while(r>q[i].r) {
            int p=belong[rk[r]];
            vis[p]--;
            if(!vis[p]&&p) cur--, ans2[p]+=i-pre[p];
            r--;
        }
        ans1[q[i].id]=cur;
    }
    for(int i=1; i<=N; ++i) if(vis[i]) ans2[i]+=cntq+1-pre[i]; //別忘了有些同學還沒有離開區間呀
    for(int i=1; i<=M; ++i) printf("%d\n", ans1[i]);
    for(int i=1; i<=N; ++i) printf("%d%c", ans2[i], " \n"[i==N]);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章