1. 程式人生 > >【BZOJ5417】你的名字(NOI2018)-字尾自動機+主席樹

【BZOJ5417】你的名字(NOI2018)-字尾自動機+主席樹

測試地址:你的名字
做法:本題需要用到字尾自動機+主席樹。
首先考慮l=1,r=|S|的情況。考慮T的每個字首的貢獻,我們需要找到它最短的沒在S中出現過,而且沒在T的前面部分出現過的字尾,這樣包含它的所有後綴就都是合法的貢獻了。顯然這個字尾的長度等同於,在S中出現過或者在T的前面部分出現過的最長的字尾的長度加1。因此我們分開考慮兩種情況。對於在T的前面部分出現過的最長字尾,可以通過對T建字尾自動機,利用字尾樹的性質,決定每個點所代表的串在哪個字首中第一次出現,DFS一遍統計貢獻即可。而對於在S中出現過的最長字尾,顯然就是用TS的字尾自動機中匹配,匹配的長度就是最長字尾長度。這樣我們就能解決68分的部分了。
再考慮拓展到

l,r任意的情況。l,r的變化對上面的做法有影響的只有在S自動機中的匹配。其實沒什麼大的變化,只不過我們每次需要判斷走到的點合不合法。具體地,我們是需要判斷,當前匹配長度為d的情況下,在末尾新增一個字元c後,能不能走到下一個點。
首先如果目前的匹配點完全沒有字元c的指標,顯然就不能繼續走,要根據字尾連結跳回去再進行試探。如果有字元c的指標,也不一定就能走,因為那個字串雖然出現在了S中,但不一定出現在指定的區間中。這時候要判斷在某個區間中存不存在當前字串。根據字尾自動機的性質,每個點有一個Right集合,即該點所代表的字串可能出現的右端點的集合,而這個Right集合顯然就是這個點在後綴樹上的子樹中所包含的所有字首節點。我們要判斷的是,Right集合中存不存在一個x,使得xr,而且x(d+1)+1l,即l+dxr。如果我們對字尾樹維護一個DFS序,那麼這就變成了在區間中詢問存不存在一個權值在某區間內的數,這就是主席樹的經典應用了,所以我們使用主席樹來判斷能不能往下走。
還有一點要注意,如果當前不能走,並不是直接通過後綴連結回退到上面的點,因為當前點還是可能有很多長度不一的字尾的,那麼現在的問題就是在長度集合[dl,dr]中,最大的使得權值區間[l+
d,r]
在上述演算法的詢問中合法的d,這個d就是我們所回退到的串的長度,這時候再往下走就行了。這個東西可以在主席樹上分治得到(需要做點處理,詳見程式碼)。當然,如果找不到這個點,就沿著字尾連結退回上一個點。
由於回退和前進的次數都是線性的,所以以上演算法的時間複雜度是O(|T|log|S|),可以通過此題。
總的來說,這道題目考察了字尾自動機本身作為匹配工具的性質,又考察了作為輔助的字尾連結,也就是字尾樹的性質,還考察了將高階資料結構——主席樹維護DFS序的方法在後綴樹上靈活運用的能力,思路自然,碼量適中,給出題人點贊(我是認真的)。
(但據說已經是套路題了……)
以下是本人程式碼:

#include <bits/stdc++.h>
using namespace std;
const int inf=1000000000;
int n,m,tot,last,totp,first[2000010],tote;
int ch[2000010][26],pre[2000010],len[2000010],vis[2000010]={0};
int tim=0,in[1000010],out[1000010],mx[500010];
int totseg=0,rt[500010]={0},seg[10000010]={0},segch[10000010][2]={0};
int qlen,ql,qr;
char s[500010];
struct edge
{
    int v,next;
}e[2000010];

void extend(int rt,int c)
{
    int p,q,np,nq;
    p=last;
    np=++tot;
    len[np]=len[p]+1;
    for(int i=0;i<26;i++)
        ch[np][i]=0;
    while(p&&!ch[p][c]) ch[p][c]=np,p=pre[p];

    if (p)
    {
        q=ch[p][c];
        if (len[p]+1==len[q]) pre[np]=q;
        else
        {
            nq=++tot;
            for(int i=0;i<26;i++)
                ch[nq][i]=ch[q][i];
            len[nq]=len[p]+1;
            pre[nq]=pre[q];
            pre[q]=pre[np]=nq;
            while(p&&ch[p][c]==q) ch[p][c]=nq,p=pre[p];
        }
    }
    else pre[np]=rt;

    last=np;
}

void insert(int a,int b)
{
    e[++tote].v=b;
    e[tote].next=first[a];
    first[a]=tote;
}

void build(int start,int n)
{
    tot=last=start;
    pre[start]=len[start]=0;
    len[0]=-1;
    for(int i=0;i<26;i++)
        ch[start][i]=0;
    for(int i=1;i<=n;i++)
    {
        extend(start,s[i]-'a');
        vis[last]=i;
    }

    tote=0;
    for(int i=start;i<=tot;i++)
        first[i]=0;
    for(int i=start+1;i<=tot;i++)
        insert(pre[i],i);
}

void pushup(int no)
{
    seg[no]=seg[segch[no][0]]+seg[segch[no][1]];
}

void insert(int &v,int last,int l,int r,int x)
{
    v=++totseg;
    seg[v]=seg[last];
    segch[v][0]=segch[last][0];
    segch[v][1]=segch[last][1];
    if (l==r) {seg[v]++;return;}
    int mid=(l+r)>>1;
    if (x<=mid) insert(segch[v][0],segch[last][0],l,mid,x);
    else insert(segch[v][1],segch[last][1],mid+1,r,x);
    pushup(v);
}

void dfs1(int v)
{
    in[v]=tim+1;
    if (vis[v])
    {
        tim++;
        insert(rt[tim],rt[tim-1],1,n,vis[v]);
    }
    for(int i=first[v];i;i=e[i].next)
        dfs1(e[i].v);
    out[v]=tim;
}

void dfs2(int v)
{
    if (!vis[v]) vis[v]=inf;
    for(int i=first[v];i;i=e[i].next)
    {
        dfs2(e[i].v);
        vis[v]=min(vis[v],vis[e[i].v]);
    }
    for(int i=first[v];i;i=e[i].next)
        if (vis[v]!=vis[e[i].v])
            mx[vis[e[i].v]]=len[v];
    if (v==totp+1) mx[vis[v]]=0;
}

int querysum(int vr,int vl,int l,int r,int s,int t)
{
    if (l>=s&&r<=t) return seg[vr]-seg[vl];
    int mid=(l+r)>>1,ans=0;
    if (s<=mid) ans+=querysum(segch[vr][0],segch[vl][0],l,mid,s,t);
    if (t>mid) ans+=querysum(segch[vr][1],segch[vl][1],mid+1,r,s,t);
    return ans;
}

int querylast(int vr,int vl,int l,int r,int s,int t)
{
    if (seg[vr]-seg[vl]==0) return 0;
    if (l==r) return l;
    int mid=(l+r)>>1,ans=0;
    if (t>mid) ans=querylast(segch[vr][1],segch[vl][1],mid+1,r,s,t);
    if (ans) return ans;
    if (s<=mid) ans=querylast(segch[vr][0],segch[vl][0],l,mid,s,t);
    return ans;
}

void solve()
{
    int now=1,d=0,l,r;
    long long ans=0;
    scanf("%d%d",&l,&r);
    for(int i=1;i<=qlen;i++)
    {
        int c=s[i]-'a';
        while(now)
        {
            if (!ch[now][c]) {now=pre[now],d=len[now];continue;}
            int s=len[pre[now]]+1,t=min(d,r-l);
            if (s>t||l+s>r) {now=pre[now],d=len[now];continue;}
            int rta=rt[out[ch[now][c]]],rtb=rt[in[ch[now][c]]-1];
            if (l+t+1>r||querysum(rta,rtb,1,n,l+t+1,r)==0)
            {
                int x=querylast(rta,rtb,1,n,l+s,l+t);
                if (!x) {now=pre[now],d=len[now];continue;}
                now=ch[now][c],d=x-l+1;break;
            }
            else {now=ch[now][c],d=t+1;break;}
        }
        if (!now) now=1,d=0;
        ans+=(long long)(i-max(mx[i],d));
    }
    printf("%lld\n",ans);
}

int main()
{
    scanf("%s",s+1);
    s[0]='#';
    n=strlen(s)-1;
    build(1,n);
    totp=tot;

    dfs1(1);

    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%s",s+1);
        qlen=strlen(s)-1;
        for(int j=totp+1;j<=totp+(qlen<<1)+1;j++)
            vis[j]=0;
        build(totp+1,qlen);

        dfs2(totp+1);
        solve();
    }

    return 0;
}

相關推薦

BZOJ5417名字NOI2018-字尾自動機+主席

測試地址:你的名字 做法:本題需要用到字尾自動機+主席樹。 首先考慮l=1,r=|S|l=1,r=|S|的情況。考慮TT的每個字首的貢獻,我們需要找到它最短的沒在SS中出現過,而且沒在TT的前面部分出現過的字尾,這樣包含它的所有後綴就都是合法的貢獻了。顯然這

[BZOJ5417/UOJ#395/NOI2018]名字字尾自動機+主席

Address Solution… 68pts:l=1,r=∣S∣l=1,r=|S|l=1,r=∣S∣ 建議先做:[BZOJ4566][Haoi2016]找相同字元 詢問前先建出 SSS 的字尾自動機,然後求出 TTT 的每個字首 T[1...i]T[1...

模板負環spfa

sizeof 貪心 com image 一行 clas 存在 cst -m 洛谷——P3385 【模板】負環 題目描述 暴力枚舉/SPFA/Bellman-ford/奇怪的貪心/超神搜索 輸入輸出格式 輸入格式: 第一行一個正整數T表

模板矩陣加速數列

cst opera name 結果 ++ 取余 int 數列 names 題目描述 a[1]=a[2]=a[3]=1 a[x]=a[x-3]+a[x-1] (x>3) 求a數列的第n項對1000000007(10^9+7)取余的值。 輸入輸出格式 輸入格式: 第一行一

SVM入門線性分類器的求解——問題的轉化,直觀角度

content cli 樣本 image ges 五個 是你 角度 spa SVM入門(六)線性分類器的求解——問題的轉化,直觀角度 讓我再一次比較完整的重復一下我們要解決的問題:我們有屬於兩個類別的樣本點(並不限定這些點在二維空間中)若幹,如圖, 圓形的樣本點定為正樣

leetcodeWord Breakpython

條件 text for -m 是我 tex eas sso false 思路是這種。我們從第一個字符開始向後依次找,直到找到一個斷句的地方,使得當前獲得的子串在dict中,若找到最後都沒找到。那麽就是False了。 在找到第一個後,接下來找下一個斷句處,當然是從第

JMeter學習參數化

一個 ngx adr conf 英文逗號 .net 註意 itl ron JMeter也有像LR中的參數化,本篇就來介紹下JMeter的參數化如何去實現。 參數化:錄制腳本中有登錄操作,需要輸入用戶名和密碼,假如系統不允許相同的用戶名和密碼同時登錄,或者想更好的模擬多個

JMeter學習元件的作用域與執行順序

ces ner 處理器 規則 fig 子節點 控制器 conf 節點 1.元件的作用域 JMeter中共有8類可被執行的元件(測試計劃與線程組不屬於元件),這些元件中,取樣器是典型的不與其它元件發生交互作用的元件,邏輯控制器只對其子節點的取樣器有效,而其它元件(config

JMeter學習錄制腳本

使用 get 運行 喜歡 錄制完成 帶來 免費 sdn title ---------------------------------------------------------------------------------------------------- 環境

P1939 模板矩陣加速數列

include algo pid str ostream 格式 矩陣加速 continue pri 鏈接: P1939 【模板】矩陣加速(數列) 題目描述 a[1]=a[2]=a[3]=1 a[x]=a[x-3]+a[x-1] (x>3) 求a數列的第n項對

Fortinet飛塔FortiGate防火墻低端產品命令行下配置RIP

rip fortinet forgate 飛塔防火墻 命令行配置rip 前言:FortiGate中端、高端產品支持web頁面配置RIP/OSPF/BGP,低端(桌面級)產品不支持,只支持CLI配置------雖然官網有手冊(英文版),但沒有實際案例,並給出建立連接的結果來的舒服~~這就是此

貪心紀念品分組P1094

scanf name print 所有 namespace 整數 數據 %d 輸出格式 題目描述 元旦快到了,校學生會讓樂樂負責新年晚會的紀念品發放工作。為使得參加晚會的同學所獲得 的紀念品價值相對均衡,他要把購來的紀念品根據價格進行分組,但每組最多只能包括兩件紀念品,

4 簡單繪圖

dispose alt draw bsp rom 形狀 .html yellow tex 在上一篇裏已經向大家介紹了如何使用GDI+繪制簡單的圖像,這一篇繼續向大家介紹其它一些繪圖知識. 1.首先我們來看下上一篇中我們使用過的Pen. Pen的屬性主要有: Color(顏色

luogu_1939 模板矩陣加速數列

iostream urn 加速 spa con () truct highlight ems #include <cstdio> #include <iostream> #include <cstring> using namespac

VBA 入門

文件的 方法 記錄 glob 數字 數據 list 目錄 處理 VBA語言的基礎認識 由 vietdung90 創建,最後一次修改 2016-10-18 【轉自W3CSCHOOL】 第一節:標識符 一、定義 標識符是一種標識變量、常量、過程、函數、類等語言構

CODEFORCES 891B Gluttony構造

ray markdown tinc lower blog clu include first right codeforces 891B Gluttony 鏈接:http://codeforces.com/problemset/problem/891/B Descripti

UVA201 Squares模擬

ref size || for != %d else eof mark 題目 題目 ? ? 分析 記錄一下再預處理一下。 ? ? 代碼 #include <bits/stdc++.h> int main() { int t=0,s,n; wh

UVa208 Firetruckdfs

-m main truck 開始 clear %d span 需要 from 題目 題目 ? ? 分析 一開始不信lrj的話,沒判聯通,果然T了。 沒必要全部跑一遍判,只需要判斷一下有沒有點與n聯通,鄰接表不太好判,但無向圖可以轉換成去判n與什麽聯通。 關於為什麽要判,還

LightOJ1336Sigma Function數論

【LightOJ1336】Sigma Function(數論) 題面 Vjudge 求和運算是一種有趣的操作,它來源於古希臘字母σ,現在我們來求一個數字的所有因子之和。例如σ(24)=1+2+3+4+6+8+12+24=60.對於小的數字求和是非常的簡單,但是對於大數字求和就比較困難了。現在給你一個n,你需要

洛谷P3803 模板多項式乘法FFT fft

n+1 swap 提示 接下來 bug ret const define %d 題目 這是一道FFT模板題 輸入格式 給定一個n次多項式F(x),和一個m次多項式G(x)。 請求出F(x)和G(x)的卷積。 輸出格式 第一行2個正整數n,m。 接下來一行n+1個數字,從低到