Count New String

题意:

  1. 定义字符串函数 \(f(S,x,y)(1\le x\le y\le n)\),返回一个长度为y-x+1的字符串,第 i 位是 \(max_{i=x...x+k-1}S_i\)
  2. 设集合\(A = {f(f(S, x_1,y_1),x_2-x_1+1,y_2-x_1+1)|1\le x_1 \le x_2 \le y_2 \le y_2 \le n}\)
  3. 求集合A 的大小
  4. \(N\le 1e5\) 字符集大小 <=10

分析:

先放出官方题解

方法一

核心点1比较容易想到,进一步可以观察到他们之间有很大一部分后面是重复的,感性的想到如果倒着插入Trie树,在Trie树上面的节点可能不会很多。具体证明来讲,当前字符 \(i\), 最近的大于它的字符的位置是 \(j (j > i)\), 那么在将位置 \(i + 1\) 的字符插入到Trie树之后,还要把长度 \((j - i)\) 的字符串插入到Trie树中。考虑一个 \(j_2\),再找一个最大的 \(j_1\) 有 \(S_{j_1} \ge S_{j_2}\),那么\([j_1,j_2]\)这个区间最多利用10次,所以Trie树节点不超过10N。然后就是常规的在Trie树上面建立广义后缀自动机,扫一遍所有节点即可得到本质不同的子串个数

#include<bits/stdc++.h>
//#define ONLINE_JUDGE
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
#define dbg(x...) do { cout << "\033[32;1m" << #x <<" -> "; err(x); } while (0)
void err() { cout << "\033[39;0m" << endl; }
template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); } const int N = 2000000 + 5; //N为字符串长度两倍
const int P = 10;
char s[N];
struct node{
int link, len, trans[P];
void clear(){
memset(trans,0, sizeof trans);
link = len = 0;
}
};
struct SAM{
node S[N];
int p, np, size;
int b[N], c[N];
SAM():p(1),np(1),size(1){}
void clear(){
for(int i=0;i<=size;i++)S[i].clear();
np = size = p = 1;
}
void insert(char ch){
int x = ch - 'a';
np = ++size;
S[np].len = S[p].len + 1;
while(p != 0 && !S[p].trans[x]) S[p].trans[x] = np, p = S[p].link;
if(p == 0)S[np].link = 1;
else{
int q, nq;
q = S[p].trans[x];
if(S[q].len == S[p].len + 1) S[np].link = q;
else{
nq = ++size;
S[nq] = S[q];
S[nq].len = S[p].len + 1;
S[np].link = S[q].link = nq;
while(p != 0 && S[p].trans[x] == q) S[p].trans[x] = nq, p = S[p].link;
}
}
p = np;
}
}sam;
int pos[N];
int n; int main(){
#ifndef ONLINE_JUDGE
freopen("i.in","r",stdin);
// freopen("o.out,"w",stdout);
#endif
scanf("%s", s+1);
n = strlen(s+1);
stack<int> st;
pos[n+1] = 1;
st.push(n+1);
for(int i=n;i>=1;i--){
while(st.size() != 1 && s[st.top()] < s[i]) st.pop();
int k = st.top();
sam.p = pos[k];
for(int j=i;j<k;j++) sam.insert(s[i]);
pos[i] = sam.p;
st.push(i);
} ll res = 0;
for(int i = 2;i<=sam.size;i++){
res += sam.S[i].len - sam.S[sam.S[i].link].len;
}
printf("%lld\n", res);
return 0;
}

方法二

然后讲解一下场上自己想出来的一个做法:

观察到\(f(S, x, y)\),最多只会有 10 段连续的一样的字符。采取这样的键值对表示法:从序列\(abcdefghij\)中抽连续的某一个部分,比如aabbc可以用\(\{"abc", [2, 2, 1]\}\) 来表示, bccd 可以用\(\{"bcd", [1, 2, 1]\}\)来表示。注意到这样的表示中,缩减的字符串最多只会有1024种。每个字符串对应的序列的长度最大是10。

对于一个后缀\(f(S, i, n)\),从中可以提取出来的键值对最多有100个。所以整个字符串,可以提取出来的键值对极限只有1e7,实际上会小很多。

对于键值相同的情况,比如 bccdde 与 bcccde, bccddee,将它们一起处理,"bcde" 对应的值是一个二维数组\([[1, 2, 2, 1], [1, 3, 1, 1], [1, 2, 2, 2]]\)。然后重点思考如何处理这个二位数组,因为我们利用"bcde" 去产生子串,具体的选择上面3个串中的某个时,c和d字符的个数是确定的,b和e的个数可以控制。

然后进一步观察,bccdde与bccddee是同一类,因为其中c和d的数量是一样的,而bcccde是另外一类。我们要对他们进行归类,也就是对二维数组\([[1, 2, 2, 1], [1, 3, 1, 1], [1, 2, 2, 2]]\)中的一位数组元素归类。

如何归类?通过他们的第2到len-1个元素的值进行归类。这个操作可以用set实现,具体来说,起初他们都属于一个集合,然后遍历第2到len-1个数字,遍历所有集合,然后根据当前这个数字,对集合进行拆分即可。

最后考虑如何计算对答案的贡献即可。比如上面的例子,bccdde与bccddee归为一类之后,只考虑b和e的个数:\(\{[1, 1], [1, 2]\}\), 对答案的贡献就是2。(这里处理起来还有一些细节需要注意)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
#define dbg(x...) do { cout << "\033[32;1m" << #x <<" -> "; err(x); } while (0)
void err() { cout << "\033[39;0m" << endl; }
template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); }
const int N = 100000 + 5;
int n;
char s[N];
int next_big[N]; // 下个比它大的字符
int next_not_equ[N]; // 下个不等于的位置
map<string, int> mp;
vector<vector<int>> vv[2000];
set<int> st[2000010];
int vis[N];
int totn;
int get(string str){
if(mp.count(str)) return mp[str];
mp[str] = ++totn;
return totn;
}
ll solve(){
ll res = 0;
for(int ii = 1; ii <= totn; ii ++){
vector<vector<int>> &tt = vv[ii];
int len = tt[0].size();
int n = tt.size();
// 一开始所有的都属于一个集合,这个集合里面应该有一个最大值
int set_count = 1;
ll Max = 0;
for(int i=0;i<n;i++) st[i].clear();
for(int i = 0; i < n ;i++){
st[0].insert(i);
Max = max(Max, 1ll * tt[i][0]);
}
if(len == 1) {
res += Max;
continue;
}
for(int r = 1; r < len-1; r ++){
// 拆集合了
int new_set_count = set_count;
for(int j = 0; j < set_count; j++){
int num = 0;
vector<int> tmp;
set<int> temp = st[j];
st[j].clear();
for(auto x : temp){
tmp.push_back(tt[x][r]);
if(vis[tt[x][r]] == -1) {
if(num == 0) vis[tt[x][r]] = j;
else {
vis[tt[x][r]] = new_set_count ++;
}
num ++;
}
int index = vis[tt[x][r]];
st[index].insert(x);
}
for(auto x : tmp) {
vis[x] = -1;
}
}
set_count = new_set_count;
}
for(int j = 0; j < set_count; j++){
vector<pair<int,int>> t;
for(auto x : st[j]){
t.push_back({tt[x][0], tt[x][len-1]});
}
sort(t.begin(), t.end());
int rmax = t.back().second;
for(int i=t.size()-1;i>=0;i--){
t[i].second = max(t[i].second, rmax);
rmax = t[i].second;
}
for(int i=0;i<t.size();i++){
ll l = t[i].first, r = t[i].second;
if(i != 0 && l == t[i-1].first) continue;
if(i == 0) {
res += l * r;
} else
res += (l - t[i-1].first) * r;
}
}
}
return res;
} int main(){
#ifndef ONLINE_JUDGE
freopen("i.in","r",stdin);
// freopen("o.out,"w",stdout);
#endif
memset(vis, -1, sizeof vis);
scanf("%s", s+1);
n = strlen(s+1);
int aft[10];
for(int i=0;i<10;i++) aft[i] = n + 1;
for(int i = n; i >= 1; i--){
s[i] = s[i] - 'a';
}
aft[s[n]] = n;
next_big[n] = next_not_equ[n] = n + 1;
for(int i=n-1;i>=1;i--){
next_big[i] = n + 1;
for(int j=s[i]+1;j<10;j++){
if(aft[j] != n + 1) {
next_big[i] = min(next_big[i], aft[j]); // 要找最近的
}
}
aft[s[i]] = i;
if(s[i] == s[i+1]) next_not_equ[i] = next_not_equ[i+1];
else next_not_equ[i] = i + 1;
}
int l = 1;
while(l <= n){
string str = "";
int r = l, pre = 0;
vector<int> v;
do {
pre = r;
r = next_big[r];
str += char('0' + s[pre]);
v.push_back(r - pre);
int id = get(str);
vv[id].push_back(v);
}while(r <= n);
l = next_not_equ[l];
}
printf("%lld\n", solve());
return 0;
}

2020牛客暑期多校训练营(第四场) C - Count New String (字符串,广义后缀自动机,序列自动机)的更多相关文章

  1. 2020牛客暑期多校训练营(第一场)H Minimum-cost Flow

    Minimum-cost Flow 题目:给n个点,m条边.接下来m行包含(a,b,c),即a,b之间有单位流量代价为c的边.接下来有q个问题,每个问题给定(x,y),即假设每条边的容量为x/y时,从 ...

  2. 2020牛客暑期多校训练营(第二场) Boundary

    传送门:Boundary  题意:给你n个点的坐标,问最多有多少个点可以在同一个圆上,(0,0)必须在这个圆上. 题解:三个点确定一个圆,所以暴力枚举两个点和(0,0)组成的圆,如果三个点不共线的话, ...

  3. 2020牛客暑期多校训练营(第一场)Easy Integration

    传送门:J. Easy Integration 题意:给你n,求这个积分,最后的结果分子是记为p,分母记为q. 求(p*q-1)mod 998244353. 题解:比赛完看到巨巨说这是贝塔函数,我一搜 ...

  4. 2020牛客暑期多校训练营 (第二场) All with Pairs

    传送门:All with Pairs 题意:给你n个字符串,求出,f(si,sj)的意思是字符串 si 的前缀和字符串 sj 后缀最长相等部分. 题解:先对所有的字符串后缀hash,用map记录每个h ...

  5. 2020牛客暑期多校训练营(第二场)Fake Maxpooling

    传送门:Fake Maxpooling 题意:给出矩阵的行数n和列数m,矩阵 Aij = lcm( i , j )  ,求每个大小为k*k的子矩阵的最大值的和. 题解:如果暴力求解肯定会t,所以要智取 ...

  6. 2020牛客暑期多校训练营(第二场) F.Fake Maxpooling (单调队列)

    题意:有一个\(n\)x\(m\)的矩阵,\(A_{i,j}=lcm(i,j)\),对于每个\(k\)x\(k\)的子矩阵,其最大元素贡献给答案,求答案的最大值. 题解:矩阵构成我们直接\(i*j/g ...

  7. 2020牛客暑期多校训练营(第四场)BCFH

    BCFH B. Basic God Problem 题意 给出c和n,求fc(n). 题解 递归到最后 fc 函数肯定等于1,那么就变成了求c被乘了几次,只要找到 x 最多能被分解成多少个数相乘就好了 ...

  8. 2019牛客暑期多校训练营(第九场)A:Power of Fibonacci(斐波拉契幂次和)

    题意:求Σfi^m%p. zoj上p是1e9+7,牛客是1e9:  对于这两个,分别有不同的做法. 前者利用公式,公式里面有sqrt(5),我们只需要二次剩余求即可.     后者mod=1e9,5才 ...

  9. 2019牛客暑期多校训练营(第一场)A题【单调栈】(补题)

    链接:https://ac.nowcoder.com/acm/contest/881/A来源:牛客网 题目描述 Two arrays u and v each with m distinct elem ...

  10. 2019牛客暑期多校训练营(第一场) B Integration (数学)

    链接:https://ac.nowcoder.com/acm/contest/881/B 来源:牛客网 Integration 时间限制:C/C++ 2秒,其他语言4秒 空间限制:C/C++ 5242 ...

随机推荐

  1. Dota游戏匹配的所有组合

    在Dota游戏中有一种匹配玩法,任意5人以下玩家组队,加入匹配系统,由系统组合出5人 vs 5人的组合进行游戏,比如2人+3人  vs 1人+4人.抽象出这个问题,就变成两边各有m个玩家,最多允许n个 ...

  2. postgresql-从表中随机获取一条记录

    目录 postgresql如何从表中高效的随机获取一条记录 随机获取一条记录random() 改写1 改写2 改写3 对比 注意 结语 postgresql如何从表中高效的随机获取一条记录 selec ...

  3. linux mysql source 导入大文件报错解决办法

    找到mysql的配置文件目录 my.cnf interactive_timeout = 120wait_timeout = 120max_allowed_packet = 500M 在导入过程中可能会 ...

  4. 隐马尔科夫模型(HMM)原理详解

    隐马尔可夫模型(Hidden Markov Model,HMM)是可用于标注问题的统计学习模型,描述由隐藏的马尔可夫链随机生成观测序列的过程,属于生成模型.HMM在语音识别.自然语言处理.生物信息.模 ...

  5. Vijos-P1103题解【线段树】

    本文为原创,转载请注明:http://www.cnblogs.com/kylewilson/ 题目出处: https://www.vijos.org/p/1103 题目描述: 一条马路从数轴0到L,每 ...

  6. Mysql简要概述

    Mysql学习笔记 Mysql简介: ​ Mysql是一个轻量级关系型数据库管理系统,由瑞典Mysql AB公司开发,目前属于Oracle公司.目前Mysql被广泛地应用在Internet上的中小型网 ...

  7. CSS3+JS完美实现放大镜模式

    最近看到一篇讲放大镜的文章,实践后感觉效果非常好,这里分享给大家. 效果如下: 其实现核心: CSS函数,如:calc() -- 动态计算:var() -- 使用自定义变量 CSS伪元素:::befo ...

  8. 手动添加Ini4idea,解决pycharm无法打开ini文件

    1. 查看本地pycharm的版本号 help -> about 2. 进入官网:http://plugins.jetbrains.com,选中pycharm及相关版本,搜索ini,切到版本,下 ...

  9. 图像分类学习:X光胸片诊断识别----迁移学习

    引言   刚进入人工智能实验室,不知道是在学习机器学习还是深度学习,想来他俩可能是一个东西,查阅之后才知道这是两个领域,或许也有些交叉,毕竟我也刚接触,不甚了解.   在我还是个纯度小白之时,写下这篇 ...

  10. 改变JavaScript中函数的内部this指向!

    改变JavaScript中函数的内部this指向! 第一种方法 call call 可以 调用函数 + 改变函数内的this指向! var obj = { name: 'lvhang' } funct ...