https://vjudge.net/problem/%E8%AE%A1%E8%92%9C%E5%AE%A2-T2372
题目
阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。打字机上只有28个按键,分别印有26个小写英文字母和'B'、'P'两个字母。
经阿狸研究发现,这个打字机是这样工作的:
l 输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最后)。
l 按一下印有'B'的按键,打字机凹槽中最后一个字母会消失。
l 按一下印有'P'的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失。
例如,阿狸输入aPaPBbP,纸上被打印的字符如下:
a
aa
ab
我们把纸上打印出来的字符串从1开始顺序编号,一直到n。打字机有一个非常有趣的功能,在打字机中暗藏一个带数字的小键盘,在小键盘上输入两个数(x,y)(其中1≤x,y≤n),打字机会显示第x个打印的字符串在第y个打印的字符串中出现了多少次。
阿狸发现了这个功能以后很兴奋,他想写个程序完成同样的功能,你能帮助他么?
1<=N<=10^5
1<=M<=10^5
输入总长<=10^5
#Sample Input aPaPBbP 3 1 2 1 3 2 3 #Sample Output 2 1 0
题解
刚抄了一遍AC自动机板子的时候高兴地去做这道题,直接套板子,然后就悲剧了……
如果直接暴力查询,会发现时间复杂度是O(NM),直接TLE
然后就不会了……
实际上每次查询只是判断从trie的根节点到第x个字符串的末尾中,有多少节点,通过fail指针不断前进,可以指向y字符串的末尾
如果把fail指针反向,会发现这些指针形成了一棵树,除了trie的根节点,每个节点都只有一个父亲(最长后缀)
那么只是统计,从trie的根节点到第x个字符串的末尾中,有多少个节点是y字符串末尾的后代
然后就有个没见过的操作= =,因为前序遍历在树上形成的时钟有个特殊的性质,后代的时钟包含在前面的时钟区间内,因此可以直接用树状数组统计后代个数
时间复杂度$O(NMlog N)$,还是TLE
然后发现可以离线,没必要每次都从头走,到相应节点的时候再计算,时间复杂度$O((N+M)log N)$,可以过了
由于数据范围比较大,还需要加个快读
AC代码
#include<cstdio> #include<cctype> #include<cstring> #include<queue> #include<stack> using namespace std; char buf[1<<21], *p1=buf, *p2=buf; #define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++) template<class T> inline void read(T&x) { x=0; char ch; do ch=getchar(); while(!isdigit(ch)); do {x=x*10+ch-'0'; ch=getchar();} while(isdigit(ch)); } #define MAXN 100007 char S[MAXN]; int ans[MAXN]; namespace ql { int hd[MAXN], to[MAXN], nxt[MAXN];} namespace fl { int hd[MAXN], to[MAXN], nxt[MAXN], tn;} namespace ac { int trie[MAXN][26], sz, fail[MAXN]; int ite[MAXN]; int L[MAXN], R[MAXN], dfsclock; inline void init(int x) { memset(trie[x],0,sizeof trie[x]); } void dfs(int p) { using namespace fl; L[p]=++dfsclock; for(int i=hd[p]; ~i; i=nxt[i]) { dfs(to[i]); } R[p]=dfsclock; } inline void calcf() { using namespace fl; tn=0; memset(hd,-1,sizeof hd); queue<int> q; fail[0]=0; for(int i=0; i<26; i++) if(trie[0][i]) { q.push(trie[0][i]); fail[trie[0][i]]=0; nxt[tn]=hd[0]; hd[0]=tn; to[tn]=trie[0][i]; tn++; } while(!q.empty()) { int now=q.front(); q.pop(); for(int i=0; i<26; i++) { int t=trie[now][i]; if(!t) { trie[now][i] = trie[fail[now]][i]; continue; } fail[t]=trie[fail[now]][i]; nxt[tn]=hd[fail[t]]; hd[fail[t]]=tn; to[tn]=t; tn++; q.push(t); } } dfsclock=0; dfs(0); } inline void build() { int l=strlen(S); stack<int> st; init(0); sz=0; int now=0, id=1; for(int i=0; i<l; i++) { if(S[i]=='B') { if(st.size()) { now = st.top(); st.pop(); } else now=0; } else if(S[i]=='P') { ite[id]=now; id++; } else { st.push(now); int t=trie[now][S[i]-'a']; if(!t) { t=trie[now][S[i]-'a']=++sz; init(t); } now=t; } } calcf(); } int fw[MAXN]; inline void opa(int x, int y) { for(;x<=dfsclock; x+=x&-x) { fw[x]+=y; } } inline int opq(int x) { int ans=0; for(;x;x-=x&-x) { ans+=fw[x]; } return ans; } inline void calcans() { memset(fw,0,sizeof fw); int l=strlen(S); stack<int> st; int now=0; int id=1; for(int i=0; i<l; i++) { if(S[i]=='B') { if(st.size()) { opa(L[now],-1); now=st.top(); st.pop(); } else now=0; } else if(S[i]=='P') { using namespace ql; for(int i=hd[id]; ~i; i=nxt[i]) { int t=ite[to[i]]; ans[i]=opq(R[t])-opq(L[t]-1); } id++; } else { st.push(now); now = trie[now][S[i]-'a']; opa(L[now],1); } } } }; int main() { scanf("%s", S); int m; read(m); using namespace ql; memset(hd,-1,sizeof hd); for(int i=0; i<m; i++) { int x,y; read(x); read(y); nxt[i]=hd[y]; to[i]=x; hd[y]=i; } ac::build(); ac::calcans(); for(int i=0; i<m; i++) printf("%d ", ans[i]); }