生活随笔
收集整理的這篇文章主要介紹了
洛谷 - P3975 [TJOI2015]弦论(后缀自动机)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
題目鏈接:點擊查看
題目大意:給出一個字符串 s,再給出一次詢問,詢問分為兩種類型:
0 k:如果不同位置的相同子串算作一個,求第 k 小的子串1 k:如果不同位置的相同子串算作多個,求第 k 小的子串
題目分析:溫故而知新,稍微總結一下這次重學 SAM 的一點小收貨吧:
SAM 中的連邊只有兩種,一種是 trie 圖上的,一種是 parent 樹上的,前者是 DAG,后者是樹一般看到 SAM 會配合基數排序然后倒著維護答案,這個過程實際上模擬的是在 parent 樹上的 dfs,更直觀的理解就是,將 parent 樹建出來,然后直接在樹上維護信息即可如果想要維護 dp 的話,在 trie 圖上需要在 DAG 上跑拓撲,在 parent 樹上跑樹形 dp順著 trie 圖跑的話可以得到子串的前綴,順著 parent 樹往上跳 father 的話可以得到后綴的后綴(應該可以這樣理解)
回到這個題目中來,第一個問題就是如何在 SAM 上表示子串,因為從源點開始順著 trie 圖一直走,每到一個新的節點就代表著一個新的子串,所以在 trie 圖上拓撲排序一下求出可達的路徑數,實質上就是求出了從某個點開始,繼續走下去可以到達的子串個數
第二個問題就是,如何區分 “位置不同的相同子串算一個” 和 “位置不同的相同子串算多個”,首先對于 trie 圖而言,其每個不同的節點都代表著一個本質不同的子串,所以初始時將每個節點的 cnt[ i ] 置為 1,然后在 DAG 上拓撲計算一下路徑的個數就好了
如果位置不同的串算多個,也就是說需要額外維護一下每個節點 endpos 的個數,因為 endpos 的定義就是 “以當前節點為后綴在原串中出現的位置 的 集合”,維護 endpos 位置的個數這個在構建自動機時,令所有非復制而來的節點賦值為 1 ,最后在 parent 樹上 dp 一下就好了,然后再同上在 trie 圖上拓撲一下就能求出路徑個數
至于求解的話,dfs 貪心去找合適的路徑就可以了
代碼:
?
//#pragma GCC optimize(2)
//#pragma GCC optimize("Ofast","inline","-ffast-math")
//#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
#include<bitset>
using namespace std;typedef long long LL;typedef unsigned long long ull;const int inf=0x3f3f3f3f;const int N=1e6+100;char s[N];LL cnt[N<<1],sum[N<<1];vector<int>node[N<<1];bool vis[N<<1];struct Node
{int ch[26];int fa,len;
}t[N<<1];int tot,last;int newnode()
{tot++;for(int i=0;i<26;i++)t[tot].ch[i]=0;t[tot].fa=t[tot].len=0;return tot;
}void add(int x)
{int p=last,np=last=newnode();t[np].len=t[p].len+1;cnt[np]=1;while(p&&!t[p].ch[x])t[p].ch[x]=np,p=t[p].fa;if(!p)t[np].fa=1;else{int q=t[p].ch[x];if(t[p].len+1==t[q].len)t[np].fa=q;else{int nq=newnode();t[nq]=t[q];t[nq].len=t[p].len+1;t[q].fa=t[np].fa=nq;while(p&&t[p].ch[x]==q)t[p].ch[x]=nq,p=t[p].fa;}}
}void dfs1(int u)//parent樹上dp
{for(auto v:node[u]){dfs1(v);cnt[u]+=cnt[v];}
}void dfs2(int u)//DAG上dp
{if(vis[u])return;vis[u]=true;for(int i=0;i<26;i++){if(!t[u].ch[i])continue;dfs2(t[u].ch[i]);sum[u]+=sum[t[u].ch[i]];}
}void print(int u,int k)
{if(k<=cnt[u])return;k-=cnt[u];for(int i=0;i<26;i++){int v=t[u].ch[i];if(!v)continue;if(sum[v]<k){k-=sum[v];continue;}putchar('a'+i);print(v,k);return;}
}void init()
{last=1;tot=0;newnode();
}int main()
{
#ifndef ONLINE_JUDGE
// freopen("data.in.txt","r",stdin);
// freopen("data.out.txt","w",stdout);
#endif
// ios::sync_with_stdio(false);int op,k;init();scanf("%s%d%d",s,&op,&k);int n=strlen(s);for(int i=0;i<n;i++)add(s[i]-'a');for(int i=1;i<=tot;i++)node[t[i].fa].push_back(i);dfs1(1);for(int i=1;i<=tot;i++)sum[i]=op?cnt[i]:(cnt[i]=1);sum[1]=cnt[1]=0;dfs2(1);if(k>sum[1])puts("-1");elseprint(1,k);return 0;
}
?
總結
以上是生活随笔為你收集整理的洛谷 - P3975 [TJOI2015]弦论(后缀自动机)的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。