BZOJ4502: 串

题意:给定一个由n个串组成的集合(n<=10000,len<=30),求组成不同好串的方案数。其中好串的定义为:可被分割成两个非空串且分别对应原集合中两个串(可相同)的前缀。

首先直接弄肯定不行,会算重。所以要么除掉相同方案或者找到一种不重不漏的计算方式。这里提供一种不重不漏的dp。

AC自动机上dp。我们定义dp状态dp[i][j]表示第一次失配之后我们加了i个字符,当前在j节点的方案数。

转移如下:dp[i][j]->dp[i+1][next[j][a]]其中a为我们当前枚举的转移字符,并且要满足条件:状态next[j][a]的长度要大于i。

现在来解释这样为什么能做到不重不漏。dp状态里的这个i其实并不能完全说是加了i个字符,也许说走了i步更加合适,因为我们dp完后去看对应的方案其实不一定就是在分割点后加了i个字符。我们基于的思想是这样的:我们在初始化的时候,枚举AC自动机上哪些节点没有当前枚举字符的转移,但是fail指向的又不是root,这些作为我们的第一“切割点”(待会解释为什么这里是打引号)。然后我们开始往里面丢字符,也许我们丢了之后转移到的状态对应的长度是大于我们目前加的这后面一段总长度的,但这不要紧,我们可以把多的那一部分前缀补到分割点前面去,也就是说分割点前移,所以说为什么前面要打引号,是这个原因。

举个例子,我们在前面一部分串为ABC,我们在这个C后面断开开始找第二段。此时第一“切割点”就是这个C。假如我们枚举一个D,而后缀自动机把它转移到了BCD状态上,我们可以视作将切割点迁移至了A后面,最后对应的分割方式就是A|BCD。

好,那么为什么要满足那个长度要大于i的条件呢?因为一旦小等i,就说明没有一个对应的方案可以实现,这是不存在的。

再回到本质问题上来,为什么能不重不漏。经过多番讨论,我想到一个较为准确的表述:我们发现,我们用这种分割方式,第一“分割点”一定会选择一个位置,使得实际分割点到第一分割点尽可能的长,这就使得每个串有一个唯一的划分方式,使得不重不漏。

上面说了不重,还要注意不漏。有一些串在我们初始化的时候是断不开的,对于这些串我们要特判一下,一开始就计入答案。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define LL long long
 4 #define N 300005
 5
 6 inline LL read(){
 7        LL x=0,f=1; char a=getchar();
 8        while(a<‘0‘ || a>‘9‘) {if(a==‘-‘) f=-1; a=getchar();}
 9        while(a>=‘0‘ && a<=‘9‘) x=x*10+a-‘0‘,a=getchar();
10        return x*f;
11 }
12
13 int n,cnt,next[N][26],f[N],root,lim,dep[N];
14 bool tr[N][26];
15 LL ans,dp[100][N];
16 queue<int>q;
17
18 inline void insert(){
19     char tmp[35]; memset(tmp,0,sizeof(tmp)); scanf("%s",tmp+1);
20     int len=strlen(tmp+1),now=root;lim=max(lim,2*len);
21     for(int i=1;i<=len;i++){
22         int a=tmp[i]-‘a‘;
23         if(!next[now][a]) next[now][a]=++cnt;
24         now=next[now][a];
25     }
26 }
27
28 inline void getfail(){
29     for(int i=0;i<26;i++) if(next[root][i])    dep[next[root][i]]=1,q.push(next[root][i]);
30     while(!q.empty()){
31         int p=q.front(); q.pop();
32         for(int i=0;i<26;i++){
33             int v=next[p][i];
34             if(!v) {next[p][i]=next[f[p]][i];continue;}
35             tr[p][i]=1;
36             int k=f[p];
37             while(k && !next[k][i]) k=f[k]; k=next[k][i];
38             f[v]=k; dep[v]=dep[p]+1;
39             q.push(v);
40         }
41     }
42 }
43
44 int main(){
45     n=read();
46     for(int i=1;i<=n;i++) insert(); getfail();
47     for(int i=1;i<=cnt;i++) if(f[i]!=root) ans++;
48     for(int i=1;i<=cnt;i++)
49         for(int a=0;a<26;a++)
50             if(!tr[i][a] && next[i][a]!=root) dp[1][next[i][a]]++;
51     for(int i=1;i<=lim;i++)
52         for(int j=1;j<=cnt;j++)
53         if(dp[i][j]){
54             ans+=dp[i][j];
55             for(int a=0;a<26;a++)
56                 if(tr[j][a] || dep[next[j][a]]>=i+1) dp[i+1][next[j][a]]+=dp[i][j];
57         }
58     printf("%lld\n",ans);
59     return 0;
60 }
时间: 2024-10-11 04:12:56

BZOJ4502: 串的相关文章

字串符笔记

本章单词: quual:相等的 ignore:忽视 lower:低的 last:最后 trim:忽略不计 concatenate:链接 buffer:缓冲区 final:最终的 &预习一下代码输出结果是什么:  euqals的区别 一.字符串概述 &字符串是一系列字符组成的序列 (1)如何使用字符串: 简单来说,使用字符串主要分为两步 1.定义并初始化字符串 2.使用字符串,对字符串进行一些处理 语法: String s="hello"; 创建String对象的另外两种

最少回文串--牛客网(秋招备战专场三模)-C++方向

题目描述:一个字符串从左向右和从右向左读都完全一样则是回文串,给定一个字符串,问该字符串中的字符所能组成的最少的回文串的个数为多少 解题思路:如果一个字符出现的次数为偶数,则必能组成回文串,如果一个字符出现奇数次,只能自己组成回文串,题目中问最少的回文串数目,即求出现次数为奇数次的字符个数即可,定义a存储每个字符出现的次数,统计出现奇数次的字符的个数,即为输出 1 #include <iostream> 2 #include <string> 3 using namespace s

PHP中的抽象类与抽象方法/静态属性和静态方法/PHP中的单利模式(单态模式)/串行化与反串行化(序列化与反序列化)/约束类型/魔术方法小结

  前  言  OOP  学习了好久的PHP,今天来总结一下PHP中的抽象类与抽象方法/静态属性和静态方法/PHP中的单利模式(单态模式)/串行化与反串行化(序列化与反序列化). 1  PHP中的抽象类与抽象方法 1.什么是抽象方法?              没有方法体 {} 的方法,必须使用abstract 关键字修饰.这样的方,我们叫做抽象方法.                    abstract function say(); //    抽象方法 2.什么是抽象类?        

拼接字符串;字符反转;统计大串中小串出现的次数

package Homework; import java.util.ArrayList;import java.util.Iterator;import java.util.List;import java.util.Scanner;/** * 把数组中的数据按照指定个格式拼接成一个字符串举例:int[] arr = {1,2,3}; 输出结果:[1, 2, 3] 字符串反转举例:键盘录入"abc" 输出结果:"cba" 统计大串中小串出现的次数举例:在字符串&q

利用冒泡排序实现一串字符串从小到大的排序

实现B/S架构,输入一串字母或数字,将它们按从小到大排序,排序算法在服务端实现. 以下是实现: Test.java import java.io.IOException;import java.io.PrintWriter; import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.ser

LeetCode Generate Parentheses 构造括号串(DFS简单题)

题意: 产生n对合法括号的所有组合,用vector<string>返回. 思路: 递归和迭代都可以产生.复杂度都可以为O(2n*合法的括号组合数),即每次产生出的括号序列都保证是合法的. 方法都是差不多的,就是记录当前产生的串中含有左括号的个数cnt,如果出现右括号,就将cnt--.当长度为2*n的串的cnt为0时,就是答案了,如果当前cnt比剩下未填的位数要小,则可以继续装“(”,否则不能再装.如果当前cnt>0,那么就能继续装“)”与其前面的左括号匹配(无需要管匹配到谁,总之能匹配)

串模式匹配之BF和KMP算法

本文简要谈一下串的模式匹配.主要阐述BF算法和KMP算法.力求讲的清楚又简洁. 一 BF算法 核心思想是:对于主串s和模式串t,长度令为len1,len2,   依次遍历主串s,即第一次从位置0开始len2个字符是否与t对应的字符相等,如果完全相等,匹配成功:否则,从下个位置1开始,再次比较从1开始len2个字符是否与t对应的字符相等.... BF算法思路清晰简单,但是每次匹配不成功时都要回溯. 下面直接贴代码: int BF_Match(char *s, char *t) { int i=0,

串的动态顺序存储代码

由于静态顺序存储使用C语言实现有些麻烦,其次容易产生溢出,因此就不对串的静态顺序存储实现了,而是采用 动态顺序存储,使得存储串的长度可以动态分配存储空间.下面是它的实现代码 #include <stdio.h> #include <stdlib.h> #define OK 1 typedef int Status; typedef struct { char *data; int len; }String; Status strInit(String *T){ T->data

回文串问题

1.回文串的判断 #include <iostream> #include <string.h> using namespace std; //回文串的判断 bool isPalindrome(const char* src) { if(src == NULL) return true; int end = strlen(src)-1,begin = 0; while(begin < end)//从两边向中间判断,当然也可以从中间向两边判断 { if(src[begin] !