DTOJ#5007. 相似的名字

宫水三叶认为,名字相似是一件非常神奇的事情。

三叶认为每一个人的名字都有特殊的含义,而两个人名字越相似,则从某种意义上就有一定联系。

三叶认为两个名字的联系是有向的,如果我们把名字看成一个字符串,那么我们定义 f ( s , t ) f(s,t) f(s,t) 为名字 s s s 对于名字 t t t 的联系。

三叶这样定义 f ( s , t ) f(s,t) f(s,t):假设存在最大的 i i i 满足 s [ 1 … i ] = t [ ∣ t ∣ − i + 1 … ∣ t ∣ ] s[1\dots i] = t[|t|-i+1 \dots |t|] s[1i]=t[ti+1t] ,那么 f ( s , t ) f(s,t) f(s,t) 的值为 i i i ,否则 f ( s , t ) f(s,t) f(s,t) 的值为 0 0 0

换句话说, f ( s , t ) f(s,t) f(s,t) 为最长的 i i i s s s 长度为 i i i 的前缀和 t t t 长度为 i i i 的后缀相等。

三叶看到了一个班级的名单,这个班级共有 n n n 名同学,第 i i i 名同学的名字为 s i s_i si

三叶想算一算整个班级的凝聚力是多少。一个班级的凝聚力定义为 ∑ i = 1 n ∑ j = 1 n f ( s i , s j ) 2 \sum_{i=1}^{n} \sum_{j=1}^{n} f(s_i,s_j)^2 i=1nj=1nf(si,sj)2

不过三叶只想计算这个值对 998244353 998244353 998244353 取模后的结果。

第一行一个整数 n n n

接下来 n n n 行,每行一个字符串 s i s_i si ,表示一个人的名字。

一行一个整数,表示答案。

样例输入 1
3
ab
ba
aba
样例输出 1
29
样例数据 2

见下发文件。

本题采用捆绑测试。

对于所有数据,满足 1 ≤ n ≤ 1 0 5 , 1 ≤ ∑ ∣ s i ∣ ≤ 1 0 6 1\le n \le 10^5,1\le \sum |s_i| \le 10^6 1n1051si106

保证字符串均为小写字母。

我们记 S = ∑ ∣ s i ∣ S= \sum |s_i| S=si

子任务编号 n n n S S S特殊性质分值
1 1 1 ≤ 1 0 2 \le 10^2 102$ \le 10^2$ − - 5 5 5
2 2 2 ≤ 1 0 3 \le 10^3 103 ≤ 1 0 3 \le 10^3 103 − - 25 25 25
3 3 3 ≤ 1 0 5 \le 10^5 105 ≤ 1 0 5 \le 10^5 105特殊性质 A 20 20 20
4 4 4 ≤ 1 0 5 \le 10^5 105 ≤ 1 0 5 \le 10^5 105特殊性质 B 20 20 20
5 5 5 ≤ 1 0 5 \le 10^5 105 ≤ 1 0 6 \le 10^6 106 − - 30 30 30

特殊性质 A:保证 ∣ s i ∣ ≤ 10 |s_i|\le 10 si10
特殊性质 B:不保证 ∣ s i ∣ |s_i| si 随机,但是字符串的每一位随机取 a ∼ z a\sim z az 中的一个。
题解:
首先, ∣ s i ∣ |s_i| si 个前缀和后缀。若对于每个前缀和后缀都匹配,必然算重。这时考虑去重。
先算出 k m p kmp kmp n e x [ ] nex[] nex[] 数组,这时我们可以对于每一个前缀扣除 n e x nex nex 的匹配数,就是答案。

#include<bits/stdc++.h>
#define N 1000005
#define ull unsigned long long
using namespace std;
const ull P=131313131;
const int mod=998244353;
inline int read(){
	int x=0,f=1;char s=getchar();
	while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
	while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
	return x*f;
}
char s[N];
vector<int> a[N];
ull p[N];
map<ull, int> h;
int cnt[N],nex[N];
inline int power(int x,int c){
	int now=1;
	while(c){
		if(c&1)now=1ll*now*x%mod;
		x=1ll*x*x%mod;c>>=1;
	}
	return now;
}
int main(){
	int n=read();
	p[0]=1;
	for(int i=1;i<=1000000;++i)p[i]=p[i-1]*P;
	for(int i=1;i<=n;++i){
		scanf("%s",1+s);
		int len=strlen(1+s);
		a[i].push_back(len);
		for(int j=1;j<=len;++j)a[i].push_back(s[j]-'a'+1);
		ull now=0;
		for(int j=len;j;--j){
			now=(ull)p[len-j]*a[i][j]+now;
			h[now]++;
		}	
	}
	int ans=0;
	for(int i=1;i<=n;++i){
		ull now=0;
		nex[1]=0;now=a[i][1];
		if(h.find(now)!=h.end())cnt[1]=h[now];
		else cnt[1]=0;
		for(int j=2,k=0;j<=a[i][0];++j){
			k=nex[j-1];
			while(k&&a[i][k+1]!=a[i][j])k=nex[k];
			if(a[i][k+1]==a[i][j])k++;
			nex[j]=k;
			now=now*P+a[i][j];
			if(h.find(now)!=h.end())cnt[j]=h[now];
			else cnt[j]=0;
			cnt[nex[j]]-=cnt[j];
	    }
		for(int j=1;j<=a[i][0];++j){
			ans=(ans+1ll*j*j%mod*cnt[j]%mod)%mod;
		}
	}
	printf("%d\n",(ans+mod)%mod);
	return 0;
}
		
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值