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.注意清空。