BZOJ4502 串

Description

兔子们在玩字符串的游戏。首先,它们拿出了一个字符串集合S,然后它们定义一个字符串为“好”的,当且仅当它可以被分成非空的两段,其中每一段都是字符串集合S中某个字符串的前缀。
比如对于字符串集合{"abc","bca"},字符串"abb","abab"是“好”的"abb"="ab"+"b",abab="ab"+"ab"),而字符串“bc”不是“好”的。兔子们想知道,一共有多少不同的“好”的字符串。

Input

第一行一个整数n,表示字符串集合中字符串的个数
接下来每行一个字符串

Output

一个整数,表示有多少不同的“好”的字符串

Sample Input

2
ab
ac

Sample Output

9

HINT

1<=n<=10000,每个字符串非空且长度不超过30,均为小写字母组成。

题解:

       题解根本看不懂……昨天满机房问大佬问了好几个小时,大佬的办法跟题解上的不一样啊。其实弄懂以后也挺简单的。

       

       总共的组成方案有前缀的平方种,然后我们减去重复的划分方案。对于一个答案串,我们规定最右边的划分方案是合法的。考虑图中绿色的那个串,红色串是它右边第一个符合条件的划分方式。显然红色串是它的一个后缀。又红、绿串都是原字符串集中的前缀,红串又是满足上述条件中最长的一个,这就是“最长前缀匹配后缀”,恰好对应了AC自动机上fail指针的指向。此时红串会对绿串产生答案减一的贡献,每当有一个以图中蓝色串结尾的前缀。于是问题就转化为:对于每一个串和它fail指针指向的串,求有多少个以两串相差部分为后缀的前缀。AC自动机上,根到每个节点的路径都对应一个前缀。而以一个串为后缀的串的个数,就是它fail树上子树大小减一(本身不算)。于是暴枚每个串即可。注意一个串的fail指针如果指向根,则不存在一个串是它的后缀,那它一定是所在答案串中最靠右的划分方式,就不应减去了。

       附上昨天下午刚学的AC自动机

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=1e4+10;

int n,m;
char s[50];

namespace AC_Automaton {
	int tot=0,root=0;
	struct Node {
		int fa,siz,fail,ch[26];
	}tree[N*30];
	queue<int> Q;

	void Insert(char *s) {
		int len=strlen(s+1),now=root;
		for (int i=1;i<=len;i++) {
			if (!tree[now].ch[s[i]-'a']) {
				tree[now].ch[s[i]-'a']=++tot;
				tree[tot].fa=now;
			}
			now=tree[now].ch[s[i]-'a'];
		}
	}

	void GetFail() {
		for (int i=0;i<26;i++)
			if (tree[root].ch[i]) {
				tree[tree[root].ch[i]].fail=root;
				Q.push(tree[root].ch[i]);
			}
		while (!Q.empty()) {
			int u=Q.front(); Q.pop();
			for (int i=0;i<26;i++) {
				if (tree[u].ch[i]) {
					tree[tree[u].ch[i]].fail=tree[tree[u].fail].ch[i];
					Q.push(tree[u].ch[i]);
				}
				else tree[u].ch[i]=tree[tree[u].fail].ch[i];
			}
		}
	}

	void Solve() {
		for (int i=1;i<=tot;i++) {
			for (int j=tree[i].fail;j;j=tree[j].fail) tree[j].siz++;
		}
		LL ans=1ll*tot*tot;
		for (int i=1;i<=tot;i++) {
			int j=i,k=tree[i].fail;
			if (!k) continue;
			while (k) j=tree[j].fa,k=tree[k].fa;
			ans-=tree[j].siz;
		}
		printf("%lld",ans);
	}
}
using namespace AC_Automaton;

int main() {
	scanf("%d",&n);
	for (int i=1;i<=n;i++) {
		scanf("%s",s+1);
		Insert(s);
	}
	tree[root].fail=root;
	GetFail(); Solve();
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
BZOJ 2908 题目是一个数据下载任务。这个任务要求下载指定的数据文件,并统计文件中小于等于给定整数的数字个数。 为了完成这个任务,首先需要选择一个合适的网址来下载文件。我们可以使用一个网络爬虫库,如Python中的Requests库,来帮助我们完成文件下载的操作。 首先,我们需要使用Requests库中的get()方法来访问目标网址,并将目标文件下载到我们的本地计算机中。可以使用以下代码实现文件下载: ```python import requests url = '目标文件的网址' response = requests.get(url) with open('本地保存文件的路径', 'wb') as file: file.write(response.content) ``` 下载完成后,我们可以使用Python内置的open()函数打开已下载的文件,并按行读取文件内容。可以使用以下代码实现文件内容读取: ```python count = 0 with open('本地保存文件的路径', 'r') as file: for line in file: # 在这里实现对每一行数据的判断 # 如果小于等于给定整数,count 加 1 # 否则,不进行任何操作 ``` 在每一行的处理过程中,我们可以使用split()方法将一行数据分割成多个字符,并使用int()函数将其转换为整数。然后,我们可以将该整数与给定整数进行比较,以判断是否小于等于给定整数。 最后,我们可以将统计结果打印出来,以满足题目的要求。 综上所述,以上是关于解决 BZOJ 2908 数据下载任务的简要步骤和代码实现。 希望对您有所帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值