The 15th Jilin Provincial Collegiate Programming Contest F(AC自动机)

原题链接

思路:

题目是多模匹配问题故容易想到AC自动机。

每次匹配都是以自己作为母串、考虑每次匹配成功对答案的贡献

设匹配到的节点是 i

不同的字符串可能有相同的前缀,故在字典树的每个节点中要记录以他为前缀的字符串个数 cnt[ ]

当前字符串的长度其实就是深度 deep[ ]

单点的贡献就是 cnt[i]*deep[i]  答案就是所有的累和。

思路很明确敲完自信测样例 很好比样例大很多!!!

思考发现题目要求的是最长前后缀,而这么写会存在一个字符串对答案贡献多次的问题。

多次贡献其实就是回跳,以样例为例

可以发现又跳了回来!

如何解决这个问题?

问题的核心是回跳问题,借助AC自动机的性质可以发现,假如当前在节点不管怎么跳一定会跳到以 i 为结尾的字符串的最长相等前后缀的前缀节点

对单个点可以看作红线的跳法

 故我们可以把 deep [ ] 数组修改成 当前节点到最长相等前后缀的前缀节点的距离就好了。

求最长相等前后缀在每次插入新串时 kmp 就好了。

Code:

#include <iostream>
#include <map>
#include <set>
#include <queue>
#include <stack>
#include <algorithm>
#include <vector>
#include <string>
#include <iomanip>
#include <cmath>
#include <ctime>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <climits>
//#include <unordered_map>
#define guo312 std::ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define ll long long
#define Inf LONG_LONG_MAX
#define inf INT_MAX
#define endl "\n"
#define PI 3.1415926535898
using namespace std;
const int N=1e6+10;
char a[200];

int to_[200];
void kmp(){
	int len=strlen(a+1);
	for(int i=2,j=0;i<=len;i++){
		while(j&&a[i]!=a[j+1]) j=to_[j];
		if(a[i]==a[j+1]) j++;
		to_[i]=j;
	}
}

int son[N][10],cnt[N],idx=0;
int w[N],_end[N];
void insert(int id){
	int p=0,len=strlen(a+1);
	for(int i=1;i<=len;i++){
		if(!son[p][a[i]-'0']) son[p][a[i]-'0']=++idx;
		p=son[p][a[i]-'0'],cnt[p]++;
		w[p]=i-to_[i];
	}
	_end[id]=p;
}

int _next[N];
void build(){
	queue<int> p;
	for(int i=0;i<=9;i++){
		if(son[0][i]){
			p.push(son[0][i]);
		}
	}
	while(!p.empty()){
		int u=p.front(); p.pop();
		for(int i=0;i<=9;i++){
			if(!son[u][i]){
				son[u][i]=son[_next[u]][i];
			}
			else{
				_next[son[u][i]]=son[_next[u]][i];
				p.push(son[u][i]);
			}
		}
	}
}

int main(){
guo312;
	int n; cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a+1;
		kmp();
		insert(i);
	}
	build();
	ll ans=0;
	for(int i=1;i<=n;i++){
		int now=_end[i];
		while(now){
			ans+=(ll)cnt[now]*(ll)w[now];
			now=_next[now];
		}
	}
	cout<<ans;
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

要用bug来打败bug

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值