P2414 [NOI2011] 阿狸的打字機

liuboom發表於2024-12-06

P2414 [NOI2011] 阿狸的打字機

題目描述:

[NOI2011] 阿狸的打字機

題目描述

阿狸喜歡收藏各種稀奇古怪的東西,最近他淘到一臺老式的打字機。打字機上只有 \(28\) 個按鍵,分別印有 \(26\) 個小寫英文字母和 BP 兩個字母。經阿狸研究發現,這個打字機是這樣工作的:

  • 輸入小寫字母,打字機的一個凹槽中會加入這個字母(這個字母加在凹槽的最後)。
  • 按一下印有 B 的按鍵,打字機凹槽中最後一個字母會消失。
  • 按一下印有 P 的按鍵,打字機會在紙上列印出凹槽中現有的所有字母並換行,但凹槽中的字母不會消失。

例如,阿狸輸入 aPaPBbP,紙上被列印的字元如下:

a
aa
ab

我們把紙上列印出來的字串從 \(1\) 開始順序編號,一直到 \(n\)。打字機有一個非常有趣的功能,在打字機中暗藏一個帶數字的小鍵盤,在小鍵盤上輸入兩個數 \((x,y)\)(其中 \(1\leq x,y\leq n\)),打字機會顯示第 \(x\) 個列印的字串在第 \(y\) 個列印的字串中出現了多少次。

阿狸發現了這個功能以後很興奮,他想寫個程式完成同樣的功能,你能幫助他麼?

輸入格式

輸入的第一行包含一個字串,按阿狸的輸入順序給出所有阿狸輸入的字元。

第二行包含一個整數 \(m\),表示詢問個數。

接下來 \(m\) 行描述所有由小鍵盤輸入的詢問。其中第 \(i\) 行包含兩個整數 \(x, y\),表示第 \(i\) 個詢問為 \((x, y)\)

輸出格式

輸出 \(m\) 行,其中第 \(i\) 行包含一個整數,表示第 \(i\) 個詢問的答案。

資料範圍

對於 \(100\%\) 的資料,\(1\leq n\leq 10^5\)\(1\leq m\leq10^5\),第一行總長度 \(\leq 10^5\)

-------------------------------------------------------------------------------------

根本不會字串,所以我們先對詢問重拳出擊:

打字機會顯示第 x個列印的字串在第y個列印的字串中出現了多少次

顯然,可能有同一個y對應非常多個x,並且詢問離線,所以我們可以 對每一次詢問的y排序,將同一個y下所有的x一起統計答案

接下來我們考慮如何統計答案:

最暴力的辦法當然是造一個AC自動機,對於每一個詢問在AC自動機上跑一邊.然而這樣的時間複雜度並不優秀

根據大佬們的題解,AC自動機的 fail顯然可以構成一顆樹
我們考慮把這棵樹建出來,(邊的方向: t[x].fail->x)

建完樹後我們會發現,之前從AC自動機的下往上跳,而現在是從上往下搜,這樣的好處是:

你可以直接對所求y打上一個值為1標記,然後對於y下的每個x,從x開搜,搜尋到的x子樹下的權值即為詢問(x,y)的答案

我們還能發現:

在一個子樹內,dfn是連續的!
所以當我們想統計子樹內的資料時,我們就可以用 線段樹 或者 樹狀陣列 這樣 O(logn) 的資料結構來維護了

由於我又懶了我還想複習樹狀陣列
所以這裡選擇用 樹狀陣列 維護

Code

#include<bits/stdc++.h>
const int N=2e5+5;
using namespace std;
int dfn_cnt,n,m,e_cnt,tot;
int sum[N],head[N],dfn[N],low[N];
char c[N];
struct Edge{
	int to,nxt;
}e[N<<1];
void add(int x,int y)
{
	e[++e_cnt]={y,head[x]};
	head[x]=e_cnt;
}
struct que{
	int x,y,id,ans;
}q[N];
bool cmp1(que q1,que q2){return q1.y<q2.y;}
bool cmp2(que q1,que q2){return q1.id<q2.id;}
struct Trie{
	int ch[26],ch_[26];
	int fail,fa,id;
}t[N];
int lowbit(int x){return x&-x;}
void upd(int x,int val){while(x<=dfn_cnt)sum[x]+=val,x+=lowbit(x);}
int query(int x){int res=0;while(x)res+=sum[x],x-=lowbit(x);return res;}
int rt[N],ql[N],qr[N];
void get_fail()
{
	queue<int> Q;
	for(int i=0;i<26;i++)
	{
		if(t[0].ch[i])Q.push(t[0].ch[i]);
	}
	while(!Q.empty())
	{
		int u=Q.front();Q.pop();
		for(int i=0;i<26;i++)
		{
			if(t[u].ch[i])
			{
				t[t[u].ch[i]].fail=t[t[u].fail].ch[i];
				Q.push(t[u].ch[i]);
			}
			else t[u].ch[i]=t[t[u].fail].ch[i];
		}
	}
}
void get_dfn(int x)
{
	dfn[x]=++dfn_cnt;
	for(int i=head[x];i;i=e[i].nxt)
	{
		int to=e[i].to;
		get_dfn(to);
	}
	low[x]=dfn_cnt;
}
void dfs(int x)
{
	upd(dfn[x],1);
	if(t[x].id)
	{
		for(int i=ql[t[x].id];i<=qr[t[x].id];i++)
		{
			q[i].ans=query(low[rt[q[i].x]])-query(dfn[rt[q[i].x]]-1);
		}
	}
	for(int i=0;i<26;i++)
	{
		if(t[x].ch_[i])
		{
			dfs(t[x].ch_[i]);
		}
	}
	upd(dfn[x],-1);
}
void work()
{
	scanf("%s",c+1);
	int len=strlen(c+1);int now=0;
	//cout<<"len:"<<len<<"\n";
	for(int i=1,cc;i<=len;i++)
	{
		if('a'<=c[i]&&c[i]<='z')
		{
			cc=c[i]-'a';
			if(!t[now].ch[cc])t[now].ch[cc]=++tot,t[tot].fa=now;
			now=t[now].ch[cc];
		}
		if(c[i]=='B')now=t[now].fa;
		if(c[i]=='P')
		{
			rt[++n]=now;
			t[now].id=n;
		}
		//cout<<now<<" ";
	}
	for(int i=0;i<=tot;i++)
	{
		for(int j=0;j<26;j++)
		{
			t[i].ch_[j]=t[i].ch[j];
		}
	}
	cin>>m;
	get_fail();
	for(int i=1;i<=tot;i++)
	{
		add(t[i].fail,i);
	}
	get_dfn(0);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&q[i].x,&q[i].y);
		q[i].id=i;
	}
	sort(q+1,q+1+m,cmp1);
	for(int i=1,now=1;i<=m;i=now)
	{
		ql[q[i].y]=i;
		while(q[i].y==q[now].y)now++;
		qr[q[i].y]=now-1;
	}
	dfs(0);
	sort(q+1,q+1+m,cmp2);
	for(int i=1;i<=m;i++)
	{
		printf("%d\n",q[i].ans);
	}
}
int main()
{
	freopen("P2414.in","r",stdin);//freopen("P2414.out","w",stdout);
	work();
}

相關文章