后缀数组 ---- 2018~2019icpc焦作H题[后缀数组+st表+二分+单调栈]
题目链接
题目大意:
给出nnn个数,定义f[l,r]f[l,r]f[l,r]表示 区间[l,r][l,r][l,r]的最大值,求所有 子区间的最大值的和,要求相同的子区间只能算一次
比如数列 5 6 5 6 , 区间 [1,2][ 1, 2 ][1,2] 和 [3,4][3,4][3,4]是一模一样的,所以只能算一次。
解题思路:
1.首先知道我们知道对于后缀数组的里面排好序的两个后缀它们的前缀是很相似的。
2.从题目出发因为相同都子串不能计算,那么我们就可以想到后缀数组或者后缀自动机去求。
3.我们想一下对于一个字符串它的不同的子串的个数是n∗(n−1)2\frac{n*(n-1)}{2}2n∗(n−1)
有多少个是重复计算的呢?
重复计算的其实就是Hegiht数组的那一部分,因为对与每个后缀,具有相同前缀的在排序之后都是一起的
4.如下图:因为每个后缀的其实位置都是不同的那么就刚好和我们枚举区间起点一样,对于每个后缀假设区间的左端点就这串头,依次枚举右端点。但是我们知道对于LCPLCPLCP部分前面的字符串已经计算过了,那么我们就只用计算后面红色框起来的部分。这样就可以保证不算重复了。
6.我们先将问题泛化:
给定nnn个数,对于每个位置,如何求出以它为左端点的所有区间的最大值的和,这个问题与上图的区别就是计算了LCPLCPLCP的部分,我们一会再想办法解决
对于数 xxx ,他的贡献只能是 从右端点是它本身的区间开始,向右延伸,直到大于xxx,因为右端点在向后,区间最大值就大于xxx了,不能算xxx的贡献
上图所示的区间才能算xxx的贡献
这样,方法就显然了,对于每个数,找到右边第一个大于它的位置yyy,中间的数字的个数乘上xxx再加上已经求出yyy的答案,就是xxx的答案
类似dpdpdp
dp[x]=x∗(posy−posx)+dp[posy]dp[x]=x*(pos_y-pos_x)+dp[pos_y]dp[x]=x∗(posy−posx)+dp[posy]
右边第一个大于它的位置可以用单调栈解决
问题解决了一半,怎么才能不算LCPLCPLCP的部分呢
先罗列出从左端点开始递增的一部分,为什么?因为只有这些数才产生了贡献
我们观察非LCPLCPLCP部分,它对答案的贡献就是有一部分的最大值是在LCPLCPLCP里面,就是红线部分到3之间的数个数为lenlenlen,lenlenlen后面就是dpdpdp值了。假设那个位置是pospospos,就是len∗max(a[1...pos])+dp[pos+1]len*max(a[1...pos])+dp[pos+1]len∗max(a[1...pos])+dp[pos+1]
现在我们就只要求lenlenlen就可以了,我们可以先用st表st表st表去预处理出每个区间的最大值.然后二分求解。对于一个固定一段的区间,区间最大值是递增的。
#include <bits/stdc++.h>
#define mid ((l + r) >> 1)
#define Lson rt << 1, l , mid
#define Rson rt << 1|1, mid + 1, r
#define ms(a,al) memset(a,al,sizeof(a))
#define log2(a) log(a)/log(2)
#define lowbit(x) ((-x) & x)
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define INF 0x3f3f3f3f
#define LLF 0x3f3f3f3f3f3f3f3f
#define f first
#define s second
using namespace std;
const int N = 2e6 + 10, mod = 1e9 + 9;
const int maxn = 500010;
const long double eps = 1e-5;
const int EPS = 500 * 500;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> PII;
typedef pair<ll,ll> PLL;
typedef pair<double,double> PDD;
template<typename T> void read(T &x)
{x = 0;char ch = getchar();ll f = 1;while(!isdigit(ch)){if(ch == '-')f*=-1;ch=getchar();}while(isdigit(ch)){x = x*10+ch-48;ch=getchar();}x*=f;
}
template<typename T, typename... Args> void read(T &first, Args& ... args)
{read(first);read(args...);
}//......................
//sa[l]排序是lth的后缀的开始位置
//ra[l]是起点是l的后缀排名多少
//lcp(suf(i),suf(j)) = min(H(ra[i] + 1),....H(ra[j]));
//区间最小值倍增求
//H(i)是排名第i个串和排名第i-1个串的lcp
//这个板子下标是从0开始,rank从1开始
struct SA {int sa[maxn], ra[maxn], height[maxn];int t1[maxn], t2[maxn], c[maxn];void build(int *str, int n, int m) {//str是要排序的字符串转成int, n是字符串长度,m是字符级别,就是字符最大范围for(int i = 0; i < n; ++ i) str[i] ++;//字符每一个都要大过0str[n ++] = 0;int i, j, p, *x = t1, *y = t2;for (i = 0; i < m; i++) c[i] = 0;//排序的桶for (i = 0; i < n; i++) c[x[i] = str[i]]++;//把每个字符串插入桶中for (i = 1; i < m; i++) c[i] += c[i - 1];//求个前缀和for (i = n - 1; i >= 0; i--) sa[--c[x[i]]] = i;//字符逆序找到对应自己的等级for (j = 1; j <= n; j <<= 1) {//所有长度为1,2,4,8....的子串的排序//长度够长的话就是所有的后缀排序p = 0;for (i = n - j; i < n; i++) y[p++] = i;//这是把后面补0的后缀先拿出来也一起排,这部分偏移之后补0,就这后缀长度不够j//排名为i的字符串的起始位置如果大于j那么就加入排序,就是把字符串向后偏移上次排序的长度//对于下一次排序来说,某个后缀除去上次排序关键字之后,剩下的部分也还是这个字符串里面的一个后缀,把这部分拿出来先//前面是1,那么就是和后面的字符串长度为1重合//下次就是1+1,就是和2重合//下次就是2+2,就是和4重合//这就是倍增的过程for (i = 0; i < n; i++) if (sa[i] >= j) y[p++] = sa[i] - j;for (i = 0; i < m; i++) c[i] = 0;for (i = 0; i < n; i++) c[x[y[i]]]++;for (i = 1; i < m; i++) c[i] += c[i - 1];for (i = n - 1; i >= 0; i--) sa[--c[x[y[i]]]] = y[i];swap(x, y);//x存的是上一次排序的结果,y是当前排序的结果,现在排序结束了p = 1;x[sa[0]] = 0;//把关键字和并进行排序for (i = 1; i < n; i++)x[sa[i]] = (y[sa[i - 1]] == y[sa[i]] && y[sa[i - 1] + j] == y[sa[i] + j]) ? p - 1 : p++;if (p >= n) break;m = p;//跟新}n--;for (int i = 0; i <= n; i++) ra[sa[i]] = i;for (int i = 0, j = 0, k = 0; i <= n; i++) {if (k) k--;j = sa[ra[i] - 1];while (str[i + k] == str[j + k]) k++;height[ra[i]] = k;}st_init(height, n);}int lg[maxn], table[23][maxn];void st_init(int *arr, int n) {if (!lg[0]) {lg[0] = -1;for (int i = 1; i < maxn; i++)lg[i] = lg[i / 2] + 1;}for (int i = 1; i <= n; ++i)table[0][i] = arr[i];for (int i = 1; i <= lg[n]; ++i)for (int j = 1; j <= n; ++j)if (j + (1 << i) - 1 <= n)table[i][j] = min(table[i - 1][j], table[i - 1][j + (1 << (i - 1))]);//从j开始长度是2^i次方得st表}int lcp(int l, int r) {l = ra[l], r = ra[r];if (l > r) swap(l, r);++l;int t = lg[r - l + 1];//区间长度return min(table[t][l], table[t][r - (1 << t) + 1]);}
} sa;
int a[maxn], stk[maxn], top, n;
ll val[maxn], pos[maxn];
ll dp[maxn][20];
vector<int> lis;void rmq() {int temp = (int)(log((double)n)/log(2.0));for(int j = 1; j <= temp; j ++)for(int i = 0; i <= n - (1<<j); i ++) {dp[i][j] = max(dp[i][j-1],dp[i + (1 << (j-1))][j - 1]);}
}int getmax(int s,int e) {int k=(int)(log((double)e-s+1)/log(2.0));return max(dp[s][k],dp[e-(1<<k)+1][k]);
}int main() {IOS;int T;cin >> T;while(T --) {cin >> n;lis.clear();for(int i = 0; i < n; ++ i) {cin >> a[i];lis.push_back(a[i]);dp[i][0] = a[i];}rmq();sort(lis.begin(),lis.end());lis.erase(unique(lis.begin(),lis.end()),lis.end());for(int i = 0; i < n; ++ i) a[i] = lower_bound(lis.begin(),lis.end(),a[i]) - lis.begin();a[n] = INF, top = 0;stk[top++] = n; for(int i = n - 1; i >= 0; -- i) {//单调栈求每个点右变第一个比它大的数是哪个?while(a[i] >= a[stk[top-1]]) top --;pos[i] = stk[top-1];stk[top++] = i;}//val[i]表示i是左端点,一直延伸到n-1的所有区间的答案val[n - 1] = lis[a[n-1]]; val[n] = 0;for(int i = n - 2; i >= 0; -- i)val[i] = 1ll * lis[a[i]] * (pos[i] - i) + val[pos[i]];sa.build(a,n,lis.size()+5);ll ans = 0;for(int i = 1; i <= n; ++ i) {int h = sa.height[i];if(h == 0) {ans += val[sa.sa[i]];continue;}int maxh = getmax(sa.sa[i],sa.sa[i]+h-1);int l = sa.sa[i]+h-1, r = n;while(l < r) {int Mid = (l + r) >> 1;if(getmax(sa.sa[i],Mid) > maxh) r = mid;else l = mid + 1;}ans += 1ll * (l - (sa.sa[i]+h)) * maxh + val[l];}cout << ans << "\n";}return 0;
}
/*
1
3
2 3 3*/
后缀数组 ---- 2018~2019icpc焦作H题[后缀数组+st表+二分+单调栈]相关推荐
- luoguP5108 仰望半月的夜空 [官方?]题解 后缀数组 / 后缀树 / 后缀自动机 + 线段树 / st表 + 二分...
仰望半月的夜空 题解 可以的话,支持一下原作吧... 这道题数据很弱..... 因此各种乱搞估计都是能过的.... 算法一 暴力长度然后判断判断,复杂度\(O(n^3)\) 期望得分15分 算法二 通 ...
- sa后缀数组使用合集,包括height数组求LPC和LCS,ST表,单调队列优化。
P5546 [POI2000]公共串 所有串合在一起,每两个串放不同的字符,求一遍后缀数组,然后利用height数组求LCS即可. #include<iostream> #include& ...
- 【小f的刷题笔记】(JS)单调栈 - 下一个更大元素 LeetCode496 下一个更大元素的下标 LeetCode739 循环数组中下一个更大元素 LeetCode503
[单调栈] 单调栈:顾名思义,就是这个栈是单调的,后面也会运用这一点 模版: var nextGreater = function (nums) {let n = nums.lengthlet res ...
- 2018宁夏邀请赛网赛 G.Trouble of Tyrant(单调栈)
题意: 有n个点,2n-3条边的图.点 1 到每个点有一条边,编号相邻的两个点有一条边.q次询问,每次询问一个增量d,问图中每条边都增加 d 后,1 到 n 的最短路是多少.增量独立,不累加. 1 & ...
- Resistors in Parallel(Gym - 102028E 2018 ICPC 焦作E题 大数+规律C++版)
链接:传送门 题外话:这套题作为队内训练赛,然后找规律找炸了,后来补题,发现大家都在用java或者python写,太难了,不会啊,只会C++的萌新躲在墙角瑟瑟发抖,写下了这个C++版本的解题报告 题意 ...
- 神殿【二进制异或】2018河北省程序设计 H题
神殿 题目描述: icebound通过勤工俭学,攒了一小笔钱,于是他决定出国旅游.这天,icebound走进了一个神秘的神殿.神殿由八位守护者守卫,总共由64个门组成,每一道门后都有一个迷宫,迷宫的大 ...
- 美团2018测开编程题——改卷子魔法表
魔法表 时间限制:C/C++语言 2000MS:其他语言 4000MS 内存限制:C/C++语言 65536KB:其他语言 589824KB 题目描述: 时辰送给了她的女儿凛一块魔法表,但是魔法表的表 ...
- 2015 UESTC 数据结构专题G题 秋实大哥去打工 单调栈
秋实大哥去打工 Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://acm.uestc.edu.cn/#/contest/show/59 Descr ...
- [Leedcode][JAVA][第84题][柱状图中最大的矩形][暴力][单调栈]
[问题描述][困难] 给定 n 个非负整数,用来表示柱状图中各个柱子的高度.每个柱子彼此相邻,且宽度为 1 .求在该柱状图中,能够勾勒出来的矩形的最大面积.以上是柱状图的示例,其中每个柱子的宽度为 1 ...
最新文章
- Java/Android基础-02
- matlab创建mat格式变量并导入数据
- Android学习笔记(一) - 如果我们来设计Android
- 数据结构——图:极大小连通子图、图的存储结构、图的遍历
- 怎么把PDF文件空白页删除
- 【数据结构】集合及运算
- zoj 3620 Escape Time II
- AHOI2005航线规划 bzoj1969(LCT缩点)
- 第五站 使用winHex利器加深理解数据页
- .NET Core 跨平台发布(dotnet publish)
- 洛谷 P4016 负载平衡问题 【最小费用最大流】
- 【Linux入门学习之】Linux关机命令总结
- 启动菜单(Boot Menu)快捷键列表
- 用Portainer或UI for Docker可视化管理树莓派容器
- 二级c语言试题讲解,计算机二级C语言真题讲解.ppt
- 阿里云搭建 ftp 服务器
- 【Steam VR 2.0】自定义按键 action 发布后无效的解决办法
- 新云人才招聘系统抖音小程序申请配置教程
- 依图科技 CTO 颜水成:视觉计算—AI 算法 vs. AI 芯片
- 在使用vue脚手架之前你必须掌握的:vue的模版以及路由用法