HackerRank How Many Substrings? Solution Yashwant Parihar, May 5, 2023May 5, 2023 In this post, we will solve HackerRank How Many Substrings? Problem Solution. Consider a string of n characters, s, of where each character is indexed from 0 ton – 1. You are given q queries in the form of two integer indices: left and right. For each query, count and print the number of different substrings of $ in the inclusive range between left and right.Note: Two substrings are different if their sequence of characters differs by at least one. For example, given the string s = aab, substrings 8[0,0]=a and 8[1,1] = a are the same but substrings $[0,1] = aa and $[1,2] = ab are different. | |Input FormatThe first line contains two space-separated integers describing the respective values of nand q.The second line contains a single string denoting s.Each of the q subsequent lines contains two space-separated integers describing the respective values of left and right for a query. Output FormatFor each query, print the number of different substrings in the inclusive range betweenindex left and index right on a new line. Sample Input 0 5 5 aabaa 1 1 1 4 1 1 1 4 0 2 Sample Output 0 1 8 1 8 5 Explanation 0Given s = aabaa, we perform the following q = 5 queries:1.1 1: The only substring of a is itself, so we print 1 on a new line. 1 4: The substrings of abaa are a, b, ab, ba, aa, aba, baa, and abaa, so we print 8 on a new line. 1 1: The only substring of a is itself, so we print 1 on a new line. 1 4: The substrings of abaa are a, b, ab, ba, aa, aba, baa, and abaa, so we print 8 on a new line.5.0 2: The substrings of aab are a, b, aa, ab, and aab, so we print 5 on a new line. HackerRank How Many Substrings? Problem Solution How Many Substrings? C++ Solution #include<bits/stdc++.h> #define pii pair<int,int> #define fi first #define se second #define pb push_back #define SZ(x) ((int)((x).size())) #define rep(i,j,k) for(int i=(int)j;i<=(int)k;i++) #define per(i,j,k) for(int i=(int)j;i>=(int)k;i--) using namespace std; typedef long long LL; typedef double DB; const DB pi=acos(-1.0); const int N=100005; int go[N<<1][26],fail[N<<1],len[N<<1],tot,last; int n; int cnt=0; namespace seg{ int tag[N<<2]; LL sum[N<<2]; inline void Tag(int me,int l,int r,int v){ tag[me]+=v; sum[me]+=(r-l+1)*1ll*v; } inline void down(int me,int l,int r){ if(tag[me]==0)return; int mid=(l+r)>>1; Tag(me<<1,l,mid,tag[me]); Tag(me<<1|1,mid+1,r,tag[me]); tag[me]=0; } void add(int me,int l,int r,int x,int y,int v){ //if(l==1&&r==n)printf("add %d %d %d\n",x,y,v); if(l^r)down(me,l,r); if(x<=l&&r<=y){ Tag(me,l,r,v); return; } int mid=(l+r)>>1; if(x<=mid)add(me<<1,l,mid,x,y,v); if(y>mid)add(me<<1|1,mid+1,r,x,y,v); sum[me]=sum[me<<1]+sum[me<<1|1]; } LL ask(int me,int l,int r,int x,int y){ if(l^r)down(me,l,r); if(x<=l&&r<=y)return sum[me]; int mid=(l+r)>>1; LL ret=0; if(x<=mid)ret+=ask(me<<1,l,mid,x,y); if(y>mid)ret+=ask(me<<1|1,mid+1,r,x,y); return ret; } void Do(int pre,int now,int L,int R){ if(L>R)return; ++cnt; //printf("_%d %d %d %d\n",pre,now,L,R); if(pre)add(1,1,n,pre-R+1,pre-L+1,-1); add(1,1,n,now-R+1,now-L+1,1); } }; namespace lct{ int l[N<<2],r[N<<2],fa[N<<2]; int last[N<<2]; inline bool top(int x){return (!fa[x])||(l[fa[x]]!=x&&r[fa[x]]!=x);} inline void left(int x){ int y=fa[x];int z=fa[y]; r[y]=l[x];if(l[x])fa[l[x]]=y; fa[x]=z;if(l[z]==y)l[z]=x;else if(r[z]==y)r[z]=x; l[x]=y;fa[y]=x; } inline void right(int x){ int y=fa[x];int z=fa[y]; l[y]=r[x];if(r[x])fa[r[x]]=y; fa[x]=z;if(l[z]==y)l[z]=x;else if(r[z]==y)r[z]=x; r[x]=y;fa[y]=x; } inline void down(int x){ if(l[x])last[l[x]]=last[x]; if(r[x])last[r[x]]=last[x]; } int q[N<<2]; inline void splay(int x){ q[q[0]=1]=x; for(int k=x;!top(k);k=fa[k])q[++q[0]]=fa[k]; per(i,q[0],1)down(q[i]); while(!top(x)){ int y=fa[x];int z=fa[y]; if(top(y)){ if(l[y]==x)right(x);else left(x); } else{ if(r[z]==y){ if(r[y]==x)left(y),left(x); else right(x),left(x); } else{ if(l[y]==x)right(y),right(x); else left(x),right(x); } } } } void Access(int x,int cov){ int y=0; for(;x;y=x,x=fa[x]){ splay(x); down(x); r[x]=0; int L,R; int z=x; while(l[z])z=l[z]; L=len[fail[z]]+1; splay(z);splay(x); z=x; while(r[z])z=r[z]; R=len[z]; splay(z);splay(x); seg::Do(last[x],cov,L,R); r[x]=y; last[x]=cov; } } void SetFa(int x,int y,int po){ fa[x]=y; Access(x,po); } void split(int x,int y,int d){ splay(y); down(y); r[y]=0; fa[d]=y; splay(x); fa[x]=d; last[d]=last[x]; } }; namespace sam{ void init(){ tot=last=1; } void expended(int x,int po){ int gt=++tot;len[gt]=len[last]+1;int p=last;last=tot; for(;p&&(!go[p][x]);p=fail[p])go[p][x]=gt; if(!p){ fail[gt]=1; lct::SetFa(gt,1,po); return; } int xx=go[p][x]; if(len[xx]==len[p]+1){ fail[gt]=xx; lct::SetFa(gt,xx,po); return; } int tt=++tot; len[tt]=len[p]+1; fail[tt]=fail[xx]; int dt=fail[xx]; fail[xx]=fail[gt]=tt; lct::split(xx,dt,tt); lct::SetFa(gt,tt,po); rep(i,0,25)go[tt][i]=go[xx][i]; for(;p&&(go[p][x]==xx);p=fail[p])go[p][x]=tt; } }; int Q; char str[N]; int qL[N]; vector<int>que[N]; LL ans[N]; void Main(){ rep(i,1,n){ sam::expended(str[i]-'a',i); rep(j,0,que[i].size()-1){ int id=que[i][j]; ans[id]=seg::ask(1,1,n,qL[id],n); } } } void init(){ scanf("%d%d",&n,&Q); scanf("%s",str+1); rep(i,1,Q){ int r;scanf("%d%d",&qL[i],&r); qL[i]++;r++; que[r].pb(i); } sam::init(); } void Output(){ rep(i,1,Q)printf("%lld\n",ans[i]); } int main(){ init(); Main(); Output(); return 0; } How Many Substrings? C Sharp Solution using System; using System.Collections.Generic; using System.IO; using System.Linq; class Solution { static void Main(String[] args) { string[] tokens_n = Console.ReadLine().Split(' '); int n = Convert.ToInt32(tokens_n[0]); int q = Convert.ToInt32(tokens_n[1]); string s = Console.ReadLine(); var queries = new Query[q]; for(int a0 = 0; a0 < q; a0++){ string[] tokens_left = Console.ReadLine().Split(' '); queries[a0] = new Query(Convert.ToInt32(tokens_left[0]), Convert.ToInt32(tokens_left[1])); } SubstringCounter.solve(s.ToCharArray(), queries); foreach (Query e in queries) Console.WriteLine(e.Answer); } } public class SubstringCounter { public static void solve(char[] str, Query[] queries) { int[] suffixArray = SuffixArray.BuildSuffixArray(str); int[] suffixArrayInverse = new int[str.Length]; for (int i = 0; i < str.Length; i++) { suffixArrayInverse[suffixArray[i]] = i; } int[] lcp = SuffixArray.BuildLcpKasai(suffixArray, suffixArrayInverse, str); //defaultdict instead of List? var queriesIndexedByLeft = new List<Query>[str.Length]; for (int i = 0; i < str.Length; i++) queriesIndexedByLeft[i] = new List<Query>(); foreach (Query query in queries) queriesIndexedByLeft[query.Left].Add(query); var maximumPrefixContainingInterval = LcpIntervalHelper.BuildIntervals(lcp); SubstringSum substringSum = new SubstringSum(str.Length); for (int i = str.Length - 1; i >= 0; i--) { //No ValueTuples in hacker rank :o( Stack<Tuple<int, int>> tups = LcpIntervalHelper.GetLastSeen(i, maximumPrefixContainingInterval, suffixArrayInverse); int matchedLcp = 0; while (tups.Count > 0) { var tup = tups.Pop(); int j = tup.Item1; int newLcp = tup.Item2; substringSum.addRange(j + matchedLcp, j + newLcp - 1); matchedLcp = newLcp; } foreach (Query query in queriesIndexedByLeft[i]) { query.Answer = substringSum.getSum(query.Left, query.Right); } } } } public class Query { public int Left { get; } public int Right { get; } public long Answer { get; set; } public Query(int left, int right) { this.Left = left; this.Right = right; } } //When we want count substrings whilst we change the right value of a string for a prebuilt suffix array the standard //Lcp array is less helpful as it is not ordered in terms of the original string and will become full of holes. With holes //we would need to consider the minimum of all intervening missing LCP elements to calculate the LCP between two elements. // //So instead we flip the idea on its side to have a set of LcpIntervals. Each lcp interval contains all Suffixes in //the Suffix Array with a minimum lcp. // //At least the minimum LCP of all but the first element in the interval can be used to reduce the substring count. //For each Suffix Array item we only store the LcpInterval with the maximum MinimumLcp. This ensures at most n //intervals. //So as the right value of a query changes the problem becomes how to spot if this is the first suffix in the //interval or not. //We do this by processing the string in reverse order and noting the leftmost time a string in the interval is seen. // Each LcpInterval contains a link to its enclosing superinterval, each interval will be enclosed by one and only // one Super-Interval, apart from the root. // I suppose you could consider it to be a tree and perhaps I should have researched if an appropriate standard // pattern / data structure existed. // The curious thing is that when I look at the finished working result I notice that Left and Right of LcpInterval // don't actually appear to be needed. But if I remove them the motivating idea of the code would be impossible to // understand. I will leave them in as comments, with the caveat of all comments that as they don't // affect the correct working of the code they may be wrong. public class LcpIntervalHelper { public class LcpInterval { //public int Left { get; } //public int Right { get; set; } public LcpInterval SuperLcpInterval { get; set; } public int MinimumLcp { get; } public int LastSeen { get; set; } //public LcpInterval(int length, int left, int right, LcpInterval superLcpInterval) public LcpInterval(int length, LcpInterval superLcpInterval) { MinimumLcp = length; //Left = left; //Right = right; SuperLcpInterval = superLcpInterval; LastSeen = int.MaxValue; } public override string ToString() { return // $"{nameof(Left)}: {Left}, " + // $"{nameof(Right)}: {Right}" + $"{nameof(MinimumLcp)}: {MinimumLcp} " + $"{nameof(LastSeen)}: {LastSeen} "; } } public static Stack<Tuple<int, int>> GetLastSeen(int i, LcpInterval[] lcpIntervals, int[] saInverse) { var interval = lcpIntervals[saInverse[i]]; var ret = new Stack<Tuple<int, int>>(); int lastSeen = int.MaxValue; while (interval.SuperLcpInterval != null) { if (interval.LastSeen < lastSeen) { ret.Push(new Tuple<int, int>(interval.LastSeen, interval.MinimumLcp)); lastSeen = interval.LastSeen; } interval.LastSeen = i; interval = interval.SuperLcpInterval; } return ret; } public static LcpInterval[] BuildIntervals(int[] lcp) { // the root interval should be the only one with parent == null //var interval = new LcpInterval(0, 0, lcp.Length , null); var interval = new LcpInterval(0, null); var suffixArrayIntervals = new LcpInterval[lcp.Length]; //Step through lcp build interval assigning max count interval to each item of the initial string. //Each interval has a link to the containit interval. for (int i = 0; i < lcp.Length - 1; i++) { //start off we are in the current interval //We know we are in the same interval as indexes to the left. We might be in //a sub interval if we have a higher matching prefex to the right. suffixArrayIntervals[i] = interval; LcpInterval oldLcpInterval = interval; while (interval.MinimumLcp > lcp[i + 1] && interval.SuperLcpInterval != null) { //close the current ineterval // interval.Right = i; oldLcpInterval = interval; interval = interval.SuperLcpInterval; } if (interval.MinimumLcp < lcp[i + 1]) { if (interval != oldLcpInterval) { // We have closed a higher LCP interval but are inserting a new one between it and its previous // SuperInterval. // e.g. oldInterval was MinimumLCP = 2 with SuperInterval MinimumLCP=0 // but we now whish to open an interval MinimumLCP=1 //interval = new LcpInterval(lcp[i + 1], oldLcpInterval.Left, lcp.Length - 1, interval); interval = new LcpInterval(lcp[i + 1], interval); oldLcpInterval.SuperLcpInterval = interval; } else { // Starting a new interval from the previous interval with higher LCP //interval = new LcpInterval(lcp[i + 1], i, lcp.Length - 1, interval); interval = new LcpInterval(lcp[i + 1], interval); suffixArrayIntervals[i] = interval; } } } suffixArrayIntervals[lcp.Length - 1] = interval; return suffixArrayIntervals; } } // For the Hackerrank problem we are interested in how the matching SuffixArray LCP values to reduce the number // of substrings // // Without matching LCP prefixes the number of substrings is n=(right+1-left)(right+2-left)/2 // This is just math for SUM(i=left to right){i}. This is clearly Trivial to calculate. // // So we are only really interested in LCP values and when they should be used to reduce substring count. // When we have a matching prefix in SuffixArray index j > i. The potential to use those matching prefixes // drops off as the j extends further to the right i.e j+lcp > right. // So naively we want // for (int k = j; j < j+lcp; j++) // { // OffsetSum.UpdateValue(k, 1); // } // And we can use OffsetSum to reduce the number of Substrings as we would normally for an LCP/SuffixArray // However if lcp is > 4 potentially much > 4 this becomes slow. // So we simulate this by using a math formula // And calculate the sum using k * LinearSum(k) + OffsetSum(k) // // For instance for n = 13 // left =3 right =10 i.e Offset = [0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0 , 0, 0] // // Can be simulated with // Offset = [0, 0, 0, -2, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0 ] // Linear = [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0 ] // // Sum( 1 ) = SumOffset(1) + SumLinear(1) = 0 + 1*0 = 0 // Sum( 3 ) = SumOffset(3) + SumLinear(3) = -2 + 3*1 = 1 // Sum( 4 ) = SumOffset(4) + SumLinear(4) = -2 + 4*1 = 2 // Sum( 5 ) = SumOffset(5) + SumLinear(5) = -2+ 5*1 = 3 // // Sum( 8 ) = SumOffset(8) + SumLinear(8) = -2 + 8*1 = 6 // Sum( 9 ) = SumOffset(9) + SumLinear(9) = -2 + 9*1 = 7 // Sum( 10 ) = SumOffset(10) + SumLinear(10) = 8 + 10*0 = 8 // Sum( 11 ) = SumOffset(11) + SumLinear(11) = 8 + 11*0 = 8 // Sum( 12 ) = SumOffset(8) + SumLinear(8) = 8 + 12*0 = 8 public class SubstringSum { class FenwickTree { private long[] fenwickArray; public FenwickTree(int n) { fenwickArray = new long[n]; } public void UpdateValue(int index, long value) { while (index < fenwickArray.Length) { fenwickArray[index] += value; index += LSB(index + 1); } } private int LSB(int i) { return i & -i; } public long GetValue(int i) { long sum = 0; while (i != 0) { sum += fenwickArray[i - 1]; i -= LSB(i); } return sum; } } private FenwickTree LinearSum; private FenwickTree OffsetSum; private int n; public SubstringSum(int n) { this.n = n; LinearSum = new FenwickTree(n); OffsetSum = new FenwickTree(n); } public void add(int i, long v) { if (i < n) { OffsetSum.UpdateValue(i, v); } } public void addRange(int left, int right) { LinearSum.UpdateValue(left, 1); OffsetSum.UpdateValue(left, 1 - left); LinearSum.UpdateValue(right, -1); OffsetSum.UpdateValue(right, right); } public long getSum(int i) { long li = i; return li * LinearSum.GetValue(i + 1) + OffsetSum.GetValue(i + 1); } //inclusive right public long getSum(int left, int right) { // I don't understand the rules of implicit type conversions in intermediate calculations. // so better safe than sorry. 100k^2 is too big for an int. long rightlong = right; long leftlong = left; return (rightlong + 1 - leftlong) * (rightlong + 2 - leftlong) / 2 - getSum(right); } } public static class SuffixArray { //Naive c# sort LCP generation for test10 n=100,000 was 973 ms //This Kasai trick bought it down to 6 ms //The simple idea is that if a suffix matches k characters. The suffix generated by one character further along //in the string must match at least k-1 characters, i.e. just use the previous matching prefix one char //further along. //So we don't need to check the first k-1 chars. This gives us an O(n) sort. public static int[] BuildLcpKasai(int[] suffixArray, int[] suffixArrayInverse, char[] str) { int[] lcp = new int[suffixArray.Length]; int n = suffixArray.Length; int k = 0; for (int i = 0; i < n; i++) { int inversei = suffixArrayInverse[i]; if (inversei == 0) { k = 0; continue; } int j = suffixArray[inversei - 1]; while (i + k < n && j + k < n && str[i + k] == str[j + k]) k++; lcp[inversei] = k; if (k > 0) k--; } return lcp; } // Naive SuffixArray creation using CSharp Collection.sort was too slow approx 6 seconds for 100k // SO I used a more efficient algorithm // Code adapted from: // http://www.cs.cmu.edu/afs/cs/academic/class/15492-f07/www/handouts/ks-icalp03.pdf // Juha Karkkainen and Peter Sanders public static int[] BuildSuffixArray(char[] str) { int[] s = new int[str.Length + 3]; int[] SA = new int[str.Length]; for (int i = 0; i < str.Length; i++) s[i] = str[i]; s[str.Length] = 0; s[str.Length + 1] = 0; s[str.Length + 2] = 0; suffixArray(s, SA, str.Length, 128); return SA; } static int GetI(int[] SA12, int n0, int t) { return (SA12[t] < n0 ? SA12[t] * 3 + 1 : (SA12[t] - n0) * 3 + 2); } static bool leq(int a1, int a2, int b1, int b2) { // lexic. order for pairs return (a1 < b1 || a1 == b1 && a2 <= b2); } // and triples static bool leq(int a1, int a2, int a3, int b1, int b2, int b3) { return (a1 < b1 || a1 == b1 && leq(a2, a3, b2, b3)); } // stably sort a[0..n-1] to b[0..n-1] with keys in 0..K from r static void radixPass(int[] a, int[] b, int[] r, int rOffset, int n, int K) { // count occurrences int[] c = new int[K + 1]; // counter array for (int i = 0; i <= K; i++) c[i] = 0; // reset counters for (int i = 0; i < n; i++) c[r[rOffset + a[i]]]++; // count occurences for (int i = 0, sum = 0; i <= K; i++) { // exclusive prefix sums int t = c[i]; c[i] = sum; sum += t; } for (int i = 0; i < n; i++) b[c[r[rOffset + a[i]]]++] = a[i]; // sort } // find the suffix array SA of s[0..n-1] in {1..K}^n // require s[n]=s[n+1]=s[n+2]=0, n>=2 static void suffixArray(int[] s, int[] SA, int n, int K) { int n0 = (n + 2) / 3, n1 = (n + 1) / 3, n2 = n / 3, n02 = n0 + n2; int[] s12 = new int[n02 + 3]; s12[n02] = s12[n02 + 1] = s12[n02 + 2] = 0; int[] SA12 = new int[n02 + 3]; SA12[n02] = SA12[n02 + 1] = SA12[n02 + 2] = 0; int[] s0 = new int[n0]; int[] SA0 = new int[n0]; // generate positions of mod 1 and mod 2 suffixes // the "+(n0-n1)" adds a dummy mod 1 suffix if n%3 == 1 for (int i = 0, j = 0; i < n + (n0 - n1); i++) if (i % 3 != 0) s12[j++] = i; // lsb radix sort the mod 1 and mod 2 triples radixPass(s12, SA12, s, 2, n02, K); radixPass(SA12, s12, s, 1, n02, K); radixPass(s12, SA12, s, 0, n02, K); int name = 0, c0 = -1, c1 = -1, c2 = -1; for (int i = 0; i < n02; i++) { if (s[SA12[i]] != c0 || s[SA12[i] + 1] != c1 || s[SA12[i] + 2] != c2) { name++; c0 = s[SA12[i]]; c1 = s[SA12[i] + 1]; c2 = s[SA12[i] + 2]; } if (SA12[i] % 3 == 1) { s12[SA12[i] / 3] = name; } // left half else { s12[SA12[i] / 3 + n0] = name; } // right half } if (name < n02) { suffixArray(s12, SA12, n02, name); // store unique names in s12 using the suffix array for (int i = 0; i < n02; i++) s12[SA12[i]] = i + 1; } else // generate the suffix array of s12 directly for (int i = 0; i < n02; i++) SA12[s12[i] - 1] = i; // stably sort the mod 0 suffixes from SA12 by their first character for (int i = 0, j = 0; i < n02; i++) if (SA12[i] < n0) s0[j++] = 3 * SA12[i]; radixPass(s0, SA0, s, 0, n0, K); // merge sorted SA0 suffixes and sorted SA12 suffixes for (int p = 0, t = n0 - n1, k = 0; k < n; k++) { int i = GetI(SA12, n0, t); // pos of current offset 12 suffix int j = SA0[p]; // pos of current offset 0 suffix if (SA12[t] < n0 ? leq(s[i], s12[SA12[t] + n0], s[j], s12[j / 3]) : leq(s[i], s[i + 1], s12[SA12[t] - n0 + 1], s[j], s[j + 1], s12[j / 3 + n0])) { // suffix from SA12 is smaller SA[k] = i; t++; if (t == n02) { // done --- only SA0 suffixes left for (k++; p < n0; p++, k++) SA[k] = SA0[p]; } } else { SA[k] = j; p++; if (p == n0) { // done --- only SA12 suffixes left for (k++; t < n02; t++, k++) SA[k] = GetI(SA12, n0, t); } } } } } How Many Substrings? Java Solution import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.InputMismatchException; import java.util.List; public class How_Many_Substrings { InputStream is; PrintWriter out; String INPUT = ""; void solve() { int n = ni(), Q = ni(); char[] s = ns(n); int[][] qs = new int[Q][]; for(int z = 0;z < Q;z++){ qs[z] = new int[]{ni(), ni(), z}; } Arrays.sort(qs, new Comparator<int[]>() { public int compare(int[] a, int[] b) { return a[1] - b[1]; } }); SuffixAutomatonOfBit sa = SuffixAutomatonOfBit.build(s); sa.sortTopologically(); SuffixAutomatonOfBit.Node[] nodes = sa.nodes; int[] from = new int[nodes.length-1]; int[] to = new int[nodes.length-1]; int p = 0; for(SuffixAutomatonOfBit.Node node : nodes){ if(node.id >= 1){ from[p] = node.link.id; to[p] = node.id; p++; } } assert p == nodes.length-1; int[][] g = packU(nodes.length, from, to); int[][] pars = parents3(g, 0); int[] par = pars[0], ord = pars[1], dep = pars[2]; HeavyLightDecomposition hld = new HeavyLightDecomposition(g, par, ord, dep); int m = hld.cluspath.length; SegmentTreeOverwrite[] sts = new SegmentTreeOverwrite[m]; for(int i = 0;i < m;i++){ sts[i] = new SegmentTreeOverwrite(hld.cluspath[i].length); } int[] base = new int[n]; int qp = 0; int np = 0; long[] ft0 = new long[n+3]; long[] ft1 = new long[n+3]; long[] ans = new long[Q]; for(int i = 0;i < n;i++){ while(!(nodes[np].len == i+1 && nodes[np].original == null))np++; base[i] = np; // tr("base", base[i]); // 5 3 1 0 // 5 3 1 0 // 8 6 3 1 0 ? // aaba // delete int cur = 0; int ppos = 0; while(true){ int last = sts[hld.clus[cur]].get(hld.clusiind[cur]); if(last == -1)break; int lca = hld.lca(base[last], base[i]); // delete from lca to cur // nodes[cur].len, nodes[lca].len; int inf = last-nodes[lca].len+1; int sup = last-ppos+1; // tr("del", last, ppos, nodes[lca].len, inf, sup); // _/ addFenwick(ft0, 0, -(sup-inf)); addFenwick(ft0, sup+1, +(sup-inf)); addFenwick(ft0, inf+1, -(inf+1)); addFenwick(ft0, sup+1, +inf+1); addFenwick(ft1, inf+1, 1); addFenwick(ft1, sup+1, -1); // tr(i, restoreRangeFenwick(ft0, ft1)); ppos = nodes[lca].len; assert dep[base[i]]-dep[lca]-1 >= 0; cur = hld.ancestor(base[i], dep[base[i]]-dep[lca]-1); } // x //b a // a // paint int cx = hld.clus[base[i]]; // cluster int ind = hld.clusiind[base[i]]; // pos in cluster while(true){ sts[cx].update(0, ind+1, i); int con = par[hld.cluspath[cx][0]]; if(con == -1)break; ind = hld.clusiind[con]; cx = hld.clus[con]; } // tr(i, restoreRangeFenwick(ft0, ft1)); addFenwick(ft0, 0, i+1+1); addFenwick(ft0, i+1+1, -(i+1+1)); addFenwick(ft1, 0, -1); addFenwick(ft1, i+1+1, 1); // tr(i, restoreRangeFenwick(ft0, ft1)); while(qp < Q && qs[qp][1] <= i){ // tr(qs[qp]); ans[qs[qp][2]] = sumRangeFenwick(ft0, ft1, qs[qp][0]); qp++; } } for(long an : ans){ out.println(an); } } public static long sumFenwick(long[] ft, int i) { long sum = 0; for(i++;i > 0;i -= i&-i)sum += ft[i]; return sum; } public static void addFenwick(long[] ft, int i, long v) { if(v == 0)return; int n = ft.length; for(i++;i < n;i += i&-i)ft[i] += v; } public static int firstGEFenwick(long[] ft, long v) { int i = 0, n = ft.length; for(int b = Integer.highestOneBit(n);b != 0;b >>= 1){ if((i|b) < n && ft[i|b] < v){ i |= b; v -= ft[i]; } } return i; } public static long[] restoreFenwick(long[] ft) { int n = ft.length-1; long[] ret = new long[n]; for(int i = 0;i < n;i++)ret[i] = sumFenwick(ft, i); for(int i = n-1;i >= 1;i--)ret[i] -= ret[i-1]; return ret; } public static int findGFenwick(long[] ft, long v) { int i = 0; int n = ft.length; for(int b = Integer.highestOneBit(n);b != 0 && i < n;b >>= 1){ if(i + b < n){ int t = i + b; if(v >= ft[t]){ i = t; v -= ft[t]; } } } return v != 0 ? -(i+1) : i-1; } public static long[] buildFenwick(long[] a) { int n = a.length; long[] ft = new long[n+1]; System.arraycopy(a, 0, ft, 1, n); for(int k = 2, h = 1;k <= n;k*=2, h*=2){ for(int i = k;i <= n;i+=k){ ft[i] += ft[i-h]; } } return ft; } public static void addRangeFenwick(long[] ft0, long[] ft1, int i, long v) { addFenwick(ft1, i+1, -v); addFenwick(ft1, 0, v); addFenwick(ft0, i+1, v*(i+1)); } public static void addRangeFenwick(long[] ft0, long[] ft1, int a, int b, long v) { if(a <= b){ addFenwick(ft1, b+1, -v); addFenwick(ft0, b+1, v*(b+1)); addFenwick(ft1, a, v); addFenwick(ft0, a, -v*a); } } public static long sumRangeFenwick(long[] ft0, long[] ft1, int i) { return sumFenwick(ft1, i) * (i+1) + sumFenwick(ft0, i); } public static long[] restoreRangeFenwick(long[] ft0, long[] ft1) { int n = ft0.length-1; long[] ret = new long[n]; for(int i = 0;i < n;i++)ret[i] = sumRangeFenwick(ft0, ft1, i); for(int i = n-1;i >= 1;i--)ret[i] -= ret[i-1]; return ret; } public static class SegmentTreeOverwrite { public int M, H, N; public int[] cover; public int I = Integer.MAX_VALUE; public SegmentTreeOverwrite(int len) { N = len; M = Integer.highestOneBit(Math.max(N-1, 1))<<2; H = M>>>1; cover = new int[M]; Arrays.fill(cover, I); for(int i = 0;i < N;i++){ cover[H+i] = -1; } for(int i = H-1;i >= 1;i--){ propagate(i); } } private void propagate(int i){} public void update(int l, int r, int v){ update(l, r, v, 0, H, 1); } private void update(int l, int r, int v, int cl, int cr, int cur) { if(l <= cl && cr <= r){ cover[cur] = v; propagate(cur); }else{ int mid = cl+cr>>>1; if(cover[cur] != I){ // back-propagate cover[2*cur] = cover[2*cur+1] = cover[cur]; cover[cur] = I; propagate(2*cur); propagate(2*cur+1); } if(cl < r && l < mid){ update(l, r, v, cl, mid, 2*cur); } if(mid < r && l < cr){ update(l, r, v, mid, cr, 2*cur+1); } propagate(cur); } } public int get(int x){ int val = I; for(int i = H+x;i >= 1;i>>>=1){ if(cover[i] != I)val = cover[i]; } return val; } } public static class HeavyLightDecomposition { public int[] clus; public int[][] cluspath; public int[] clusiind; public int[] par, dep; public HeavyLightDecomposition(int[][] g, int[] par, int[] ord, int[] dep) { init(g, par, ord, dep); } public void init(int[][] g, int[] par, int[] ord, int[] dep) { clus = decomposeToHeavyLight(g, par, ord); cluspath = clusPaths(clus, ord); clusiind = clusIInd(cluspath, g.length); this.par = par; this.dep = dep; } public static int[] decomposeToHeavyLight(int[][] g, int[] par, int[] ord) { int n = g.length; int[] size = new int[n]; Arrays.fill(size, 1); for(int i = n-1;i > 0;i--)size[par[ord[i]]] += size[ord[i]]; int[] clus = new int[n]; Arrays.fill(clus, -1); int p = 0; for(int i = 0;i < n;i++){ int u = ord[i]; if(clus[u] == -1)clus[u] = p++; // centroid path (not heavy path) int argmax = -1; for(int v : g[u]){ if(par[u] != v && (argmax == -1 || size[v] > size[argmax]))argmax = v; } if(argmax != -1)clus[argmax] = clus[u]; } return clus; } public static int[][] clusPaths(int[] clus, int[] ord) { int n = clus.length; int[] rp = new int[n]; int sup = 0; for(int i = 0;i < n;i++){ rp[clus[i]]++; sup = Math.max(sup, clus[i]); } sup++; int[][] row = new int[sup][]; for(int i = 0;i < sup;i++)row[i] = new int[rp[i]]; for(int i = n-1;i >= 0;i--){ row[clus[ord[i]]][--rp[clus[ord[i]]]] = ord[i]; } return row; } public static int[] clusIInd(int[][] clusPath, int n) { int[] iind = new int[n]; for(int[] path : clusPath){ for(int i = 0;i < path.length;i++){ iind[path[i]] = i; } } return iind; } public int lca(int x, int y) { int rx = cluspath[clus[x]][0]; int ry = cluspath[clus[y]][0]; while(clus[x] != clus[y]){ if(dep[rx] > dep[ry]){ x = par[rx]; rx = cluspath[clus[x]][0]; }else{ y = par[ry]; ry = cluspath[clus[y]][0]; } } return clusiind[x] > clusiind[y] ? y : x; } public int ancestor(int x, int v) { while(x != -1){ if(v <= clusiind[x])return cluspath[clus[x]][clusiind[x]-v]; v -= clusiind[x]+1; x = par[cluspath[clus[x]][0]]; } return x; } } public static int lca2(int a, int b, int[][] spar, int[] depth) { if (depth[a] < depth[b]) { b = ancestor(b, depth[b] - depth[a], spar); } else if (depth[a] > depth[b]) { a = ancestor(a, depth[a] - depth[b], spar); } if (a == b) return a; int sa = a, sb = b; for (int low = 0, high = depth[a], t = Integer.highestOneBit(high), k = Integer .numberOfTrailingZeros(t); t > 0; t >>>= 1, k--) { if ((low ^ high) >= t) { if (spar[k][sa] != spar[k][sb]) { low |= t; sa = spar[k][sa]; sb = spar[k][sb]; } else { high = low | t - 1; } } } return spar[0][sa]; } protected static int ancestor(int a, int m, int[][] spar) { for (int i = 0; m > 0 && a != -1; m >>>= 1, i++) { if ((m & 1) == 1) a = spar[i][a]; } return a; } public static int[][] logstepParents(int[] par) { int n = par.length; int m = Integer.numberOfTrailingZeros(Integer.highestOneBit(n - 1)) + 1; int[][] pars = new int[m][n]; pars[0] = par; for (int j = 1; j < m; j++) { for (int i = 0; i < n; i++) { pars[j][i] = pars[j - 1][i] == -1 ? -1 : pars[j - 1][pars[j - 1][i]]; } } return pars; } public static int[][] parents3(int[][] g, int root) { int n = g.length; int[] par = new int[n]; Arrays.fill(par, -1); int[] depth = new int[n]; depth[0] = 0; int[] q = new int[n]; q[0] = root; for (int p = 0, r = 1; p < r; p++) { int cur = q[p]; for (int nex : g[cur]) { if (par[cur] != nex) { q[r++] = nex; par[nex] = cur; depth[nex] = depth[cur] + 1; } } } return new int[][] { par, q, depth }; } static int[][] packU(int n, int[] from, int[] to) { int[][] g = new int[n][]; int[] p = new int[n]; for (int f : from) p[f]++; for (int t : to) p[t]++; for (int i = 0; i < n; i++) g[i] = new int[p[i]]; for (int i = 0; i < from.length; i++) { g[from[i]][--p[from[i]]] = to[i]; g[to[i]][--p[to[i]]] = from[i]; } return g; } public static class SuffixAutomatonOfBit { public Node t0; public int len; public Node[] nodes; public int gen; private boolean sortedTopologically = false; private boolean lexsorted = false; private SuffixAutomatonOfBit(int n) { gen = 0; nodes = new Node[2*n]; this.t0 = makeNode(0, null); } private Node makeNode(int len, Node original) { Node node = new Node(); node.id = gen; node.original = original; node.len = len; nodes[gen++] = node; return node; } public static class Node { public int id; public int len; public char key; public Node link; private Node[] next = new Node[3]; public Node original; public int np = 0; public int hit = 0; public void putNext(char c, Node to) { to.key = c; if(hit<<~(c-'a')<0){ for(int i = 0;i < np;i++){ if(next[i].key == c){ next[i] = to; return; } } } hit |= 1<<c-'a'; if(np == next.length){ next = Arrays.copyOf(next, np*2); } next[np++] = to; } public boolean containsKeyNext(char c) { return hit<<~(c-'a')<0; // for(int i = 0;i < np;i++){ // if(next[i].key == c)return true; // } // return false; } public Node getNext(char c) { if(hit<<~(c-'a')<0){ for(int i = 0;i < np;i++){ if(next[i].key == c)return next[i]; } } return null; } public List<String> suffixes(char[] s) { List<String> list = new ArrayList<String>(); if(id == 0)return list; int first = original != null ? original.len : len; for(int i = link.len + 1;i <= len;i++){ list.add(new String(s, first - i, i)); } return list; } } public static SuffixAutomatonOfBit build(char[] str) { int n = str.length; SuffixAutomatonOfBit sa = new SuffixAutomatonOfBit(n); sa.len = str.length; Node last = sa.t0; for(char c : str){ last = sa.extend(last, c); } return sa; } public Node extend(Node last, char c) { Node cur = makeNode(last.len+1, null); Node p; for(p = last; p != null && !p.containsKeyNext(c);p = p.link){ p.putNext(c, cur); } if(p == null){ cur.link = t0; }else{ Node q = p.getNext(c); // not null if(p.len + 1 == q.len){ cur.link = q; }else{ Node clone = makeNode(p.len+1, q); clone.next = Arrays.copyOf(q.next, q.next.length); clone.hit = q.hit; clone.np = q.np; clone.link = q.link; for(;p != null && q.equals(p.getNext(c)); p = p.link){ p.putNext(c, clone); } q.link = cur.link = clone; } } return cur; } public SuffixAutomatonOfBit lexSort() { for(int i = 0;i < gen;i++){ Node node = nodes[i]; Arrays.sort(node.next, 0, node.np, new Comparator<Node>() { public int compare(Node a, Node b) { return a.key - b.key; } }); } lexsorted = true; return this; } public SuffixAutomatonOfBit sortTopologically() { int[] indeg = new int[gen]; for(int i = 0;i < gen;i++){ for(int j = 0;j < nodes[i].np;j++){ indeg[nodes[i].next[j].id]++; } } Node[] sorted = new Node[gen]; sorted[0] = t0; int p = 1; for(int i = 0;i < gen;i++){ Node cur = sorted[i]; for(int j = 0;j < cur.np;j++){ if(--indeg[cur.next[j].id] == 0){ sorted[p++] = cur.next[j]; } } } for(int i = 0;i < gen;i++)sorted[i].id = i; nodes = sorted; sortedTopologically = true; return this; } // visualizer public String toString() { StringBuilder sb = new StringBuilder(); for(Node n : nodes){ if(n != null){ sb.append(String.format("{id:%d, len:%d, link:%d, cloned:%b, ", n.id, n.len, n.link != null ? n.link.id : null, n.original.id)); sb.append("next:{"); for(int i = 0;i < n.np;i++){ sb.append(n.next[i].key + ":" + n.next[i].id + ","); } sb.append("}"); sb.append("}"); sb.append("\n"); } } return sb.toString(); } public String toGraphviz(boolean next, boolean suffixLink) { StringBuilder sb = new StringBuilder("http://chart.apis.google.com/chart?cht=gv:dot&chl="); sb.append("digraph{"); for(Node n : nodes){ if(n != null){ if(suffixLink && n.link != null){ sb.append(n.id) .append("->") .append(n.link.id) .append("[style=dashed],"); } if(next && n.next != null){ for(int i = 0;i < n.np;i++){ sb.append(n.id) .append("->") .append(n.next[i].id) .append("[label=") .append(n.next[i].key) .append("],"); } } } } sb.append("}"); return sb.toString(); } public String label(Node n) { if(n.original != null){ return n.id + "C"; }else{ return n.id + ""; } } public String toDot(boolean next, boolean suffixLink) { StringBuilder sb = new StringBuilder("digraph{\n"); sb.append("graph[rankdir=LR];\n"); sb.append("node[shape=circle];\n"); for(Node n : nodes){ if(n != null){ if(suffixLink && n.link != null){ sb.append("\"" + label(n) + "\"") .append("->") .append("\"" + label(n.link) + "\"") .append("[style=dashed];\n"); } if(next && n.next != null){ for(int i = 0;i < n.np;i++){ sb.append("\"" + label(n) + "\"") .append("->") .append("\"" + label(n.next[i]) + "\"") .append("[label=\"") .append(n.next[i].key) .append("\"];\n"); } } } } sb.append("}\n"); return sb.toString(); } } void run() throws Exception { is = INPUT.isEmpty() ? System.in : new ByteArrayInputStream(INPUT.getBytes()); out = new PrintWriter(System.out); long s = System.currentTimeMillis(); solve(); out.flush(); if(!INPUT.isEmpty())tr(System.currentTimeMillis()-s+"ms"); } public static void main(String[] args) throws Exception { new How_Many_Substrings().run(); } private byte[] inbuf = new byte[1024]; public int lenbuf = 0, ptrbuf = 0; private int readByte() { if(lenbuf == -1)throw new InputMismatchException(); if(ptrbuf >= lenbuf){ ptrbuf = 0; try { lenbuf = is.read(inbuf); } catch (IOException e) { throw new InputMismatchException(); } if(lenbuf <= 0)return -1; } return inbuf[ptrbuf++]; } private boolean isSpaceChar(int c) { return !(c >= 33 && c <= 126); } private int skip() { int b; while((b = readByte()) != -1 && isSpaceChar(b)); return b; } private double nd() { return Double.parseDouble(ns()); } private char nc() { return (char)skip(); } private String ns() { int b = skip(); StringBuilder sb = new StringBuilder(); while(!(isSpaceChar(b))){ // when nextLine, (isSpaceChar(b) && b != ' ') sb.appendCodePoint(b); b = readByte(); } return sb.toString(); } private char[] ns(int n) { char[] buf = new char[n]; int b = skip(), p = 0; while(p < n && !(isSpaceChar(b))){ buf[p++] = (char)b; b = readByte(); } return n == p ? buf : Arrays.copyOf(buf, p); } private char[][] nm(int n, int m) { char[][] map = new char[n][]; for(int i = 0;i < n;i++)map[i] = ns(m); return map; } private int[] na(int n) { int[] a = new int[n]; for(int i = 0;i < n;i++)a[i] = ni(); return a; } private int ni() { int num = 0, b; boolean minus = false; while((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-')); if(b == '-'){ minus = true; b = readByte(); } while(true){ if(b >= '0' && b <= '9'){ num = num * 10 + (b - '0'); }else{ return minus ? -num : num; } b = readByte(); } } private long nl() { long num = 0; int b; boolean minus = false; while((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-')); if(b == '-'){ minus = true; b = readByte(); } while(true){ if(b >= '0' && b <= '9'){ num = num * 10 + (b - '0'); }else{ return minus ? -num : num; } b = readByte(); } } private static void tr(Object... o) { System.out.println(Arrays.deepToString(o)); } } C# C++ HackerRank Solutions java cppCSharpHackerrank Solutionsjava