首页 > 其他分享 >HDU4787

HDU4787

时间:2024-03-30 19:45:24浏览次数:15  
标签:AC int sum sqrt HDU4787 自动机 复杂度

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

标签:AC,int,sum,sqrt,HDU4787,自动机,复杂度
From: https://www.cnblogs.com/hahaxiang/p/18105910

相关文章