P4070 [SDOI2016]生成魔咒

题意:

有n个字符xi,每次在S的末尾加入一个字符,(一开始S为空),每次加入xi后的不相同字串有多少个

题解:

做这个题首先要会后缀数组P3809 【模板】后缀排序,还要知道不同的子串如何求P2408 不同子串个数,这两个题我都有写过博客
对于一个字符串,我们在其末尾加一个新字符,对这个字符串的height等变换还是很大的,整个格局都会被打乱(因为我们是后缀数组,在最后加,所有后缀都会改变)。但是如果我们在前面加,在最前面加字符,那只会额外产生一个新字符

原本是ABCD
后缀是:ABCD,BCD,CD,D
在末尾加个E
ABCDE
后缀变成:ABCDE,BCDE,CDE,DE,E(没有一个和之前是一样的)
如果是在开头加E
EABCD
后缀就是:EABCD,ABCD,BCD,CD,D(和原先比只多了一个EABCD,我们只需要处理新增就可以)

但是题目就是让在后面加,怎么搞?
我们可以将这个字符串翻转呀,这样每次加不就是在最前面加

原本是ABCD
翻转:DCBA
加入E
得到:EDCBA
新增后缀EDCBA,现在我们要求新增不重复字串,按照结论,后缀长度减去其height[i]

height[i]如何求?
这时,只有插入的那个新后缀紧邻的height 值发生了改变,注意到这个值是可以直接在整段字符串的后缀数组中查到的,即 lcp(prev(i),post(i)) 。
height[i]的变化是O(1)的,我们用一个set每次插入后缀排名,对于以第i位开始的后缀,其排名是rk[i],他的就找他在set中的前一位(到目前位置,已有后缀的前一项),再找后项,两边分别取min(height[l~r]),这可以用st来实现。这就是重复部分,用长度len减去重复部分,就是后缀的贡献
详细看代码

代码:

// Problem: P4070 [SDOI2016]生成魔咒
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P4070
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// Data:2021-08-23 12:42:42
// By Jozky#include <bits/stdc++.h>
#include <unordered_map>
#define debug(a, b) printf("%s = %d\n", a, b);
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> PII;
clock_t startTime, endTime;
//Fe~Jozky
const ll INF_ll= 1e18;
const int INF_int= 0x3f3f3f3f;
void read(){};
template <typename _Tp, typename... _Tps> void read(_Tp& x, _Tps&... Ar)
{x= 0;char c= getchar();bool flag= 0;while (c < '0' || c > '9')flag|= (c == '-'), c= getchar();while (c >= '0' && c <= '9')x= (x << 3) + (x << 1) + (c ^ 48), c= getchar();if (flag)x= -x;read(Ar...);
}
template <typename T> inline void write(T x)
{if (x < 0) {x= ~(x - 1);putchar('-');}if (x > 9)write(x / 10);putchar(x % 10 + '0');
}
void rd_test()
{#ifdef LOCALstartTime= clock();freopen("in.txt", "r", stdin);
#endif
}
void Time_test()
{#ifdef LOCALendTime= clock();printf("\nRun Time:%lfs\n", (double)(endTime - startTime) / CLOCKS_PER_SEC);
#endif
}
const int MAXN= 100005;char ch[MAXN], all[MAXN];
int sa[MAXN], rk[MAXN], height[MAXN], tax[MAXN], tp[MAXN], a[MAXN], n, m;
char str[MAXN];
int b[MAXN];
//rk[i] 第i个后缀的排名; sa[i] 排名为i的后缀位置; height[i] 排名为i的后缀与排名为(i-1)的后缀的LCP
//tax[i] 计数排序辅助数组; tp[i] rk的辅助数组(计数排序中的第二关键字),与sa意义一样。
//a为原串
void RSort()
{//rk第一关键字,tp第二关键字。for (int i= 0; i <= m; i++)tax[i]= 0;for (int i= 1; i <= n; i++)tax[rk[tp[i]]]++;for (int i= 1; i <= m; i++)tax[i]+= tax[i - 1];for (int i= n; i >= 1; i--)sa[tax[rk[tp[i]]]--]= tp[i]; //确保满足第一关键字的同时,再满足第二关键字的要求
} //计数排序,把新的二元组排序。int cmp(int* f, int x, int y, int w)
{return f[x] == f[y] && f[x + w] == f[y + w];
}
//通过二元组两个下标的比较,确定两个子串是否相同void Suffix()
{//safor (int i= 1; i <= n; i++)rk[i]= a[i], tp[i]= i;m= 127, RSort(); //一开始是以单个字符为单位,所以(m = 127)for (int w= 1, p= 1, i; p < n; w+= w, m= p) { //把子串长度翻倍,更新rk//w 当前一个子串的长度; m 当前离散后的排名种类数//当前的tp(第二关键字)可直接由上一次的sa的得到for (p= 0, i= n - w + 1; i <= n; i++)tp[++p]= i; //长度越界,第二关键字为0for (i= 1; i <= n; i++)if (sa[i] > w)tp[++p]= sa[i] - w;//更新sa值,并用tp暂时存下上一轮的rk(用于cmp比较)RSort(), swap(rk, tp), rk[sa[1]]= p= 1;//用已经完成的sa来更新与它互逆的rk,并离散rkfor (i= 2; i <= n; i++)rk[sa[i]]= cmp(tp, sa[i], sa[i - 1], w) ? p : ++p;}//离散:把相等的字符串的rk设为相同。//LCPint j, k= 0;for (int i= 1; i <= n; height[rk[i++]]= k)for (k= k ? k - 1 : k, j= sa[rk[i] - 1]; a[i + k] == a[j + k]; ++k);//这个知道原理后就比较好理解程序
}
int st[MAXN][30];
void ST()
{for (int i= 1; i <= n; i++)st[i][0]= height[i];int w= log2(n);for (int k= 1; k <= w; k++) {for (int i= 1; i <= n; i++) {if (i + (1 << k) > n + 1)break;st[i][k]= min(st[i][k - 1], st[i + (1 << (k - 1))][k - 1]);}}
}
int get_min(int l, int r)
{int k= log2(r - l + 1);return min(st[l][k], st[r - (1 << k) + 1][k]);
}
void Init()
{read(n);for (int i= 1; i <= n; i++) {read(a[i]);b[i]= a[i];}sort(b + 1, b + 1 + n);int len= unique(b + 1, b + 1 + n) - (b + 1);reverse(a + 1, a + 1 + n);for (int i= 1; i <= n; i++) {a[i]= lower_bound(b + 1, b + 1 + len, a[i]) - b;}
}
set<int> s;int main()
{Init();Suffix();ST();// for (int i= 1; i <= n; i++)// printf("height[i]=%d\n", height[i]);ll ans= 0;for (int i= n; i; i--) { //倒序考虑s.insert(rk[i]);set<int>::iterator it;it= s.find(rk[i]);int lcp= 0;if (it != s.begin()) { //如果没到开头,说明前面有height,和前面取minint p= *(--it);lcp= get_min(p + 1, rk[i]);++it; //还原}/*注意:s.begin();返回指向容器最开始位置数据的指针而s.end();返回指向容器最后一个数据单元+1的指针*/++it;if (it != s.end()) {int p= *it;lcp= max(lcp, get_min(rk[i] + 1, p));}int len= n - i + 1;ans+= len - lcp;printf("%lld\n", ans);}
}

P4070 [SDOI2016]生成魔咒相关推荐

  1. 洛谷 P4070 [SDOI2016]生成魔咒 解题报告

    P4070 [SDOI2016]生成魔咒 题目描述 魔咒串由许多魔咒字符组成,魔咒字符可以用数字表示.例如可以将魔咒字符 \(1\).\(2\) 拼凑起来形成一个魔咒串 \([1,2]\). 一个魔咒 ...

  2. bzoj4516 / P4070 [SDOI2016]生成魔咒

    P4070 [SDOI2016]生成魔咒 后缀自动机 每插入一个字符,对答案的贡献为$len[last]-len[fa[last]]$ 插入字符范围过大,所以使用$map$存储. (去掉第35行就是裸 ...

  3. P4070 [SDOI2016]生成魔咒(SAM len数组的含义)

    [SDOI2016]生成魔咒 题目描述 魔咒串由许多魔咒字符组成,魔咒字符可以用数字表示.例如可以将魔咒字符 1 , 2 1,2 1,2 拼凑起来形成一个魔咒串 [ 1 , 2 ] [1,2] [1, ...

  4. 洛谷 P4070 [SDOI2016]生成魔咒 后缀自动机

    题目描述 魔咒串由许多魔咒字符组成,魔咒字符可以用数字表示.例如可以将魔咒字符 1.2 拼凑起来形成一个魔咒串 [1,2]. 一个魔咒串 S 的非空字串被称为魔咒串 S 的生成魔咒. 例如 S=[1, ...

  5. luogu P4070 [SDOI2016]生成魔咒

    首先有两个结论: 1.后缀自动机具有最简性,即每种不同的子串只会在sam上体现一次,体现形式是sam上一条由root出发的路径. 2.一个字符串不同子串的个数等于所有关键节点的max(x)-min(x ...

  6. P4070 [SDOI2016]生成魔咒(SAM)

    传送门 用后缀数组做的真的心累 后缀数组+ST表+反串思维+set(或平衡树)动态维护 h e i g h t height height数组 然后用 S A M SAM SAM就是模板题- 要求本质 ...

  7. [SDOI2016]生成魔咒

    4516: [Sdoi2016]生成魔咒 Time Limit: 10 Sec  Memory Limit: 128 MB Submit: 1460  Solved: 835 [Submit][Sta ...

  8. BZOJ4516: [Sdoi2016]生成魔咒

    BZOJ4516: [Sdoi2016]生成魔咒 Description 魔咒串由许多魔咒字符组成,魔咒字符可以用数字表示. 例如可以将魔咒字符 1.2 拼凑起来形成一个魔咒串 [1,2]. 一个魔咒 ...

  9. BZOJ 4516: [Sdoi2016]生成魔咒 [后缀自动机]

    4516: [Sdoi2016]生成魔咒 题意:询问一个字符串每个前缀有多少不同的子串 做了一下SDOI2016R1D2,题好水啊随便AK 强行开map上SAM 每个状态的贡献就是\(Max(s)-M ...

最新文章

  1. No module named 'xxx’
  2. 开源阿牛图像处理实验室
  3. 漫画:什么是计数排序
  4. java 数据包含_java – 包含大量数据库表的通用实现
  5. CO02工单下达时错误“订单类型 ZP91 工厂 1000: 没有检查工序的维护规则”
  6. jni4net调用net库
  7. 定风波·三月七日(苏轼)
  8. 微信公众号与小程序对接文档
  9. 简易搜索引擎原理与基于Hadoop MapReduce的搜索引擎实现
  10. 机器学习-单层感知器不能实现异或运算的原因
  11. SQLSERVER的中文排序规则
  12. 23考研——2月份计划
  13. 【新知实验室】实时音视频(TRTC)之初体验
  14. 质量、质量要求、检验、验证和确认
  15. 汇编SHR、SHL、SAR、SAL、ROL、ROR、RCL、RCR指令
  16. 留数定理构造围道计算实积分类型大全
  17. 每天坚持慢跑30分钟,一个月身体会有什么变化?
  18. Oracle 常见问题1000问(不积跬步,无以至千里)
  19. 卓越的教练是如何训练高手的
  20. 贪心算法适用条件_五大常用算法之三:贪心算法

热门文章

  1. 看完这些自动化原理图,有一种豁然开朗的感觉
  2. 头文件定义全局变量_5.2 C++局部变量与全局变量 | 输出局部全局变量
  3. selenium autoit java_Java+Selenium——AutoIt工具处理文件上传
  4. 筛选装置用c语言编程,一种空壳瓜子筛选装置的制作方法
  5. linux服务器出现黄,linux服务器出现严重故障后的原因以及解决方法
  6. linux mysql安装教程 方大帝_discuz论坛出现Can not connect to MySQL server错误的解决方法...
  7. ajax中迭代是什么意思,Ajax 局部刷新迭代器的内容
  8. mysql写入监控_zabbix 自定义key 监控mysql增删查改
  9. 设计模式在项目中的应用案例_设计模式在项目中的应用(初学者版)
  10. 大气校正后的ndvi_大气校正常见错误处理方法及校正后检查