HDU4787

SunLight_HHX發表於2024-03-30

HDU4787

來源

網上找的。

標籤

根號分治 AC自動機

題意

給出兩種操作:

· +w 表示學習一個新單詞 \(w\)

· ?p 表示詢問段落 \(p\) 中有多少個子串是之前學習過的單詞。

字串僅包含0和1,強制線上,多組資料。

加密操作如下:

對於每個出現的字串,設 \(lst\) 為上一個詢問的結果。給定給你的字串已經移動了 \(lst\) 次(字串 \(s1s2 ... sk\) 的移動版本是 \(sks1s2 ... sk-1\))。

題解

首先我們明確在離線狀態下我們如何處理操作。我們顯然是可以透過AC自動機維護兩種操作的。

插入操作等價於往AC自動機中插入一個新詞。

對於詢問操作。我們對於AC自動機上的每個節點維護一個資訊 \(num[i]\) 表示透過該節點跳失配指標可以跳到多少單詞節點。轉移很容易,\(num[i]=num[fail[i]]+word[i]\)\(word[i]\) 表示 \(i\) 是否為單詞節點)。答案即為 \(\sum_{i=1}^{|s|} num[s[i]在AC自動機上對應的節點]\)

但是在線上情況下我們每插入一個單詞就需要花費 \(O(n)\) 的代價去重構失配樹。

考慮根號重構

我們用兩個AC自動機來維護答案。小AC自動機維護當前的插入,每插入一個單詞,我們就重構小AC自動機。當小AC自動機的大小大於 \(\sqrt{\sum|s|}\) 時,我們就將小AC自動機合併到大AC自動機中,並情況小AC自動機。答案即為兩個AC自動機中的答案之和。

分析時間複雜度:

對於小AC自動機而言最多被清空 \(\sqrt{\sum|s|}\) 次,每次清空前最多重構 \(\sqrt{\sum|s|}\) 次,每次重構複雜度 \(O(\sqrt{\sum|s|})\)。總複雜度 \(O(\sum|s|\times\sqrt{\sum|s|})\)

對於大AC自動機而言最多重構 \(\sqrt{\sum|s|}\) 次,每次重構複雜度 \(O(\sum|s|)\)。總複雜度 \(O(\sum|s|\times\sqrt{\sum|s|})\)

整體複雜度 \(O(\sum|s|\times\sqrt{\sum|s|})\)

程式碼

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int BL=330;
const int N=1e5+10;
int T,n,cnt,lst,cnt1,cnt2,ch1[N][2],ch2[N][2],fail1[N],fail2[N],num1[N],num2[N];
map<string,bool>mp;
bool f1[N],f2[N];
string s;
vector<string>v1;
vector<string>v2; 
void clear1()
{
	for(int i=0;i<=cnt1;i++)
	{
		memset(ch1[i],0,sizeof(ch1[i]));
		fail1[i]=num1[i]=f1[i]=0;
	}
	cnt1=0;
	return;
}
void clear2()
{
	for(int i=0;i<=cnt2;i++)
	{
		memset(ch2[i],0,sizeof(ch2[i]));
		fail2[i]=num2[i]=f2[i]=0;
	}
	cnt2=0;
	return;
}
void insert1()
{
	cnt=0;
	clear1();
	clear2();
	v2.clear();
	for(int i=0;i<v1.size();i++)
	{
		int len=v1[i].size()-1;
		int z=0;
		for(int j=1;j<=len;j++)
		{
			int to=v1[i][j]-'0';
			if(ch1[z][to]==0)
			ch1[z][to]=++cnt1;
			z=ch1[z][to];
		}
		f1[z]=1;
		num1[z]=1;
	}
	queue<int>q;
	for(int i=0;i<=1;i++)
	{
		if(ch1[0][i]!=0)
		{
			q.push(ch1[0][i]);
		}
	}
	while(!q.empty())
	{
		int x=q.front();
		q.pop();
		for(int i=0;i<=1;i++)
		{
			if(ch1[x][i]==0)
			{
				ch1[x][i]=ch1[fail1[x]][i];
			}
			else
			{
				fail1[ch1[x][i]]=ch1[fail1[x]][i];
				q.push(ch1[x][i]);
				num1[ch1[x][i]]+=num1[fail1[ch1[x][i]]];
			}
		}
	}
	return;
}
void insert2()
{
	clear2();
	for(int i=0;i<v2.size();i++)
	{
		int len=v2[i].size()-1;
		int z=0;
		for(int j=1;j<=len;j++)
		{
			int to=v2[i][j]-'0';
			if(ch2[z][to]==0)
			ch2[z][to]=++cnt2;
			z=ch2[z][to];
		}
		f2[z]=1;
		num2[z]=1;
	}
	queue<int>q;
	for(int i=0;i<=1;i++)
	{
		if(ch2[0][i]!=0)
		{
			q.push(ch2[0][i]);
		}
	}
	while(!q.empty())
	{
		int x=q.front();
		q.pop();
		for(int i=0;i<=1;i++)
		{
			if(ch2[x][i]==0)
			{
				ch2[x][i]=ch2[fail2[x]][i];
			}
			else
			{
				fail2[ch2[x][i]]=ch2[fail2[x]][i];
				q.push(ch2[x][i]);
				num2[ch2[x][i]]+=num2[fail2[ch2[x][i]]];
			}
		}
	}
	return;
}
void solve()
{
	lst=0;
	int z1=0,z2=0;
	for(int i=1;i<=s.size()-1;i++)
	{
		int to=s[i]-'0';
		z1=ch1[z1][to];
		z2=ch2[z2][to];
		lst+=num1[z1]+num2[z2];
	}
	cout<<lst<<'\n';
	return;
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>T;
	for(int c_=1;c_<=T;c_++)
	{
		cout<<"Case #"<<c_<<":"<<'\n';
		mp.clear();
		lst=0,cnt=0;
		v1.clear(),v2.clear();
		clear1(),clear2();
		cin>>n;
		for(int i=1;i<=n;i++)
		{
			cin>>s;
			string r;
			r+=s[0];
			int rlst=lst%(s.size()-1);
			for(int j=rlst+1;j<=s.size()-1;j++)
			{
				r+=s[j];
			}
			for(int j=1;j<=rlst;j++)
			{
				r+=s[j];
			}
			s=r;
			if(s[0]=='+')
			{
				if(mp[s]==1)
				continue;
				mp[s]=1;
				v1.push_back(s);
				v2.push_back(s);
				cnt+=s.size()-1;	
				if(cnt<BL)
				insert2();
				else
				insert1();
			}
			else
			{
				solve();
			}	
		}
	}
	return 0;
}

可能的出錯點

1.插入的單詞一定要去重,不然可能會分別被大小兩個AC自動機計算到。

2.解密時需要將 \(lst\%|s|\) 不然會出問題。

3.要開longlong。

4.注意清空。