Codeforces 700E Cool Slogans 后缀数组+线段树
来源:互联网 发布:windows声音设置 编辑:程序博客网 时间:2024/05/16 19:28
Bomboslav set up a branding agency and now helps companies to create new logos and advertising slogans. In term of this problems, slogan of the company should be a non-empty substring of its name. For example, if the company name is "hornsandhoofs", then substrings "sand" and "hor" could be its slogans, while strings "e" and "hornss" can not.
Sometimes the company performs rebranding and changes its slogan. Slogan A is considered to be coolerthan slogan B if B appears in A as a substring at least twice (this occurrences are allowed to overlap). For example, slogan A = "abacaba" is cooler than slogan B = "ba", slogan A = "abcbcbe" is cooler than slogan B = "bcb", but slogan A = "aaaaaa" is not cooler than slogan B = "aba".
You are given the company name w and your task is to help Bomboslav determine the length of the longest sequence of slogans s1, s2, ..., sk, such that any slogan in the sequence is cooler than the previous one.
The first line of the input contains a single integer n (1 ≤ n ≤ 200 000) — the length of the company name that asks Bomboslav to help. The second line contains the string w of length n, that consists of lowercase English letters.
Print a single integer — the maximum possible length of the sequence of slogans of the company named w, such that any slogan in the sequence (except the first one) is cooler than the previous
3abc
1
5ddddd
5
11abracadabra
3
定义如果短串在另一个长串当中出现了至少两次,则长串比短串更酷。构造一个序列使得其中每个串都比后一个串酷,要求序列当中每个串都是给定串的子串,求这个序列的最长长度。
搞了一下午。
英文题解如下:
Let's call a good substring either a single character, or a substring starting and ending with a smaller good substring, with no other occurence of that smaller substring in the middle. A maximal chain can be transformed into another maximal chain of good substrings, so we only need to consider those.
The repeated substring of a good substring is the previous good substring in the chain.
Actually, there are exacly n good substrings : for each position i, we can consider the largest good substring starting there. It can't appear right of it, or we would have a larger good substring starting at i, and if it appears left, it can be matched with the occurence at i to make a larger substring. It also means we can build a tree on good substrings (i.e. on string positions), with an edge from s1 to s2 if s1 is the repeated substring in s2. We are interested in the depth of that tree.
Let's build the tree incrementally. We process the string from right to left (in increasing size of suffixes). We want to be able to compute at each position the size of the good substring. Assume we are at position i, with parent j. We know that substring s starts at i and j and we want to compute the size of the smallest substring starting at i with two occurences of s, i.e. the substring with exactly two occurences of s. Let's assume for now that we can know the last time we saw s. Then we know the size of the good substring of i. We can use the suffix array to find all other positions starting with s, and set i as their potential parent (i will only end up as an ancestor).
So the only thing left is to compute the last time we saw a good substring. When we see a substring, we can propagate our position upwards in the tree (update all its ancestors with it). It is however too slow, as the tree can have depth O(n). We can transform this by setting a value in one node and doing subtree minimum queries. Subtree minimum queries can be solved using a segment tree on a static tree, but here building the tree and queries are interleaved. A solution could be to use an euler tour tree, but it is actually possible to use a segment tree in this case, as even though we don't know the shape of the tree in advance, each time we add a node, we know the size of its subtree (the number of substrings starting with its good substring), so we can give it a range in the segment tree with the same size.
大意如此:
从后向前扫描字符串,对于字符串每个位置 i 我们只要记录从这个位置开始的最大答案。
对于每个位置 i ,我们假设现在已知最大的答案 t , 设从 i 开始的,答案为 t 的,长度最短的子串为s.那么t=1时length(s)=1,否则s的首尾必定包含相同的子串。我们用这个 t 在以rank为下标的线段树上更新首尾都包含s的串,这些串的范围,我们可以二分得到。具体操作是,在后缀数组以rank为下标,去找和s的height为length(s)的、包含s的区间[L,R].由于长度短的串在答案相同时更优,所以要同时更新s的长度。
那么对于每个位置 i ,我们在线段树上单点查询,这个点的最大答案就是查询结果+1.接下来,我们只要确定s的长度就好了。同样的二分height至少为在s首尾都出现的子串的length的一段[L,R],在线段树的这一段寻找开头位置最小的一个。接着,就能算出s的长度。
#include <cstdio>#include <string.h>#include <string> #include <algorithm>#define mem0(a) memset(a,0,sizeof(a))#define meminf(a) memset(a,0x3f,sizeof(a))using namespace std;typedef long long ll;typedef long double ld;typedef double db;const int maxn=200005,inf=0x3f3f3f3f; const ll llinf=0x3f3f3f3f3f3f3f3f; int wa[maxn],wb[maxn],wv[maxn],wss[maxn],sa[maxn],ranki[maxn],height[maxn];char s[maxn];int a[maxn],mn[maxn][20];int num,L,R;struct node {int max, len;};struct Tree {int lc,rc,l,r,min;node c;};Tree tree[4*maxn];node MAX(node a, node b) {if ((a.max > b.max || (a.max == b.max&&a.len < b.len))) return a; else return b;}int cmp(int *r,int a,int b,int l) {return r[a]==r[b]&&r[a+l]==r[b+l];}void build(int *r,int *sa,int n,int m) {int i,j,k,p,*x=wa,*y=wb,*t;for (i=0;i<m;i++) wss[i]=0;for (i=0;i<n;i++) wss[x[i]=r[i]]++;for (i=0;i<m;i++) wss[i]+=wss[i-1];for (i=n-1;i>=0;i--) sa[--wss[x[i]]]=i;for (j=1,p=1;p<n;j*=2,m=p) {for (p=0,i=n-j;i<n;i++) y[p++]=i;for (i=0;i<n;i++) if (sa[i]>=j) y[p++]=sa[i]-j;for (i=0;i<n;i++) wv[i]=x[y[i]];for (i=0;i<m;i++) wss[i]=0;for (i=0;i<n;i++) wss[wv[i]]++;for (i=1;i<m;i++) wss[i]+=wss[i-1];for (i=n-1;i>=0;i--) sa[--wss[wv[i]]]=y[i];t=x;x=y;y=t;p=1;x[sa[0]]=0;for (i=1;i<n;i++) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;}for (i=1;i<n;i++) ranki[sa[i]]=i;k=0; for (i=0;i<n-1;height[ranki[i++]]=k) {if (k) k--;for (j=sa[ranki[i]-1];r[i+k]==r[j+k];k++);}}void rmq(int n) {int i,j;for (i=1;i<=n;i++) mn[i][0]=height[i];for (j=1;(1<<j)<=n;j++) {for (i=1;i+(1<<j)-1<=n;i++) {mn[i][j]=min(mn[i][j-1],mn[i+(1<<(j-1))][j-1]);}}}int lcp(int fa,int fb) {if (fa>fb) swap(fa,fb);fa++;int k=0;while ((1<<(k+1))<=(fb-fa+1)) k++;return min(mn[fa][k],mn[fb-(1<<k)+1][k]);}void buildtree(int now,int l,int r) {tree[now].l=l;tree[now].r=r;tree[now].c.max=tree[now].c.len=0;tree[now].min=inf;if (l!=r) {num++;tree[now].lc=num;buildtree(num,l,(l+r)/2);num++;tree[now].rc=num;buildtree(num,(l+r)/2+1,r);}}void update1 (int now,int l,int r,int val,int len) {if (tree[now].l>=l&&tree[now].r<=r) {if (val>tree[now].c.max||(val==tree[now].c.max&&len<tree[now].c.len))tree[now].c.max=val,tree[now].c.len=len;} else {if (l<=(tree[now].l+tree[now].r)/2) update1(tree[now].lc,l,r,val,len);if (r>(tree[now].l+tree[now].r)/2) update1(tree[now].rc,l,r,val,len);//tree[now].c = MAX(tree[tree[now].lc].c, tree[tree[now].rc].c);}}void update2 (int now,int pos,int val) {if (tree[now].l==pos&&tree[now].r==pos) {tree[now].min=val;} else {if (pos<=(tree[now].l+tree[now].r)/2) update2(tree[now].lc,pos,val);if (pos>(tree[now].l+tree[now].r)/2) update2(tree[now].rc,pos,val);tree[now].min=min(tree[tree[now].lc].min,tree[tree[now].rc].min);}}node findval (int now,int pos) {if (tree[now].l==pos&&tree[now].r==pos) return tree[now].c;else if (pos<=(tree[now].l+tree[now].r)/2) return MAX(tree[now].c, findval(tree[now].lc, pos));else return MAX(tree[now].c,findval(tree[now].rc, pos));}int findmin(int now,int l,int r) {if (tree[now].l>=l&&tree[now].r<=r) {return tree[now].min;} else {int ans=inf;if (l<=(tree[now].l+tree[now].r)/2) ans=min(ans,findmin(tree[now].lc,l,r));if (r>(tree[now].l+tree[now].r)/2) ans=min(ans,findmin(tree[now].rc,l,r));return ans;}}void BinarySearch(int pos,int len,int n) {int l=1,r=pos-1;L=pos;while (l<=r) {int mid=(l+r)/2;if (lcp(mid,pos)>=len) L=mid,r=mid-1; else l=mid+1;}l=pos+1,r=n;R=pos;while (l<=r) {int mid=(l+r)/2;if (lcp(pos,mid)>=len) R=mid,l=mid+1; else r=mid-1;}}int main() {int n,i,ans=1,val,len;scanf("%d",&n);scanf("%s",s);for (i=0;i<n;i++) {a[i]=s[i]-'a'+1;}a[n]=0;build(a,sa,n+1,27);rmq(n+1);num=1;buildtree(1,1,n);for (i=n-1;i>=0;i--) {node now=findval(1,ranki[i]);if (now.max==0) val=1,len=1; else {val=now.max+1;BinarySearch(ranki[i],now.len,n);len=now.len+findmin(1,L,R)-i;}update2(1,ranki[i],i);BinarySearch(ranki[i],len,n);update1(1,L,R,val,len);ans=max(ans,val);}printf("%d\n",ans);//system("pause");return 0;}
- Codeforces 700E Cool Slogans 后缀数组+线段树
- CF 700E Cool Slogans 线段树+后缀数组贪心求解
- codeforces 700E 后缀数组
- 【codeforces】Codeforces Round #305 (Div. 1)E. Mike and Friends【后缀数组+线段树】
- Codeforces Round #305 (Div. 1)E. Mike and Friends 后缀数组+RMQ+线段树
- Codeforces Round #305 (Div. 1)E. Mike and Friends【后缀数组+线段树】
- 字符串k在第li到第ri个字符串中一共出现了几次 后缀数组+线段树 Codeforces Div. 1E. Mike and Friends
- codeforces 760E or 759C 【线段树维护后缀和】
- CodeForces - 292E 线段树
- codeforces 400e 线段树
- 线段树 CodeForces 580E
- codeforces 413E 线段树
- codeforces 242E(线段树)
- codeforces 452E Three strings 后缀数组+并查集
- Codeforces 452E Three strings 后缀数组 + 并查集
- codeforces 628E Zbazi in Zeydabad(线段树||树状数组优化)
- Codeforces 785E 题解(树套树-树状数组套线段树)
- Codeforces Round #407 (Div. 1) E. New task(线段树+树状数组)
- 随笔一篇
- op输入失调电压
- LeetCode 95. Unique Binary Search Trees II&96. Unique Binary Search Trees--动态规划,二叉树
- centos 7 安装gcc6.2编译器
- controller或者service层调用配置文件里的属性
- Codeforces 700E Cool Slogans 后缀数组+线段树
- 浅谈对栈帧的理解
- 8
- 学习笔记--斜率优化
- 未指定的错误,异常详细信息: System.Exception: 未指定的错误
- PyTorch笔记3-分类
- Python里直接开发cad
- Java套接字编程---TCP
- 算法课作业系列8——Knight Probability in Chessboard