dutin's blog

By dutin, history, 3 years ago, In English

I've seen CF tutorials for many other sections of CSES but didn't see one for strings, so I thought of writing one. Constructive criticism (or just regular criticism) is always welcome!

Note that there are many ways to do a problem. For instance, linear time string searching can be done with hashing, KMP, Z-Algorithm, Aho-Corasick Automaton, etc. . I used the way that was most intuitive for me, but there might exist other ways that have shorter code.

1. Word Combinations

Solution
Code

2. String Matching

Solution
Code

3. Finding Borders

Solution
Code

4. Finding Periods

Solution
Code

5. Minimal Rotation

Solution
Code

6. Longest Palindrome

Solution
Code

7. Required Substring

Solution
Code

Solution and Code from toniskrijelj

8. Palindrome Queries

Solution
Code

9. Finding Patterns

Solution
Code

10. Counting Patterns

Solution
Code

11. Pattern Positions

Solution
Code

12. Distinct Substring

Solution
Code

13. Repeating Substring

Solution
Code

14. String Functions

Solution
Code

15. Substring Order I

Solution
Code

16. Substring Order II

Solution
Code

17. Substring Distribution

Solution
Code
  • Vote: I like it
  • +103
  • Vote: I do not like it

| Write comment?
»
3 years ago, # |
Rev. 2   Vote: I like it +16 Vote: I do not like it

I thought I ought to mention that Substring Order 1, Substring Order 2, and Substring Distribution all have a suffix array + lcp solution as well.

Substring Order 1

Consider the solution for counting all the unique substrings, you take the sum of all lcp[i]. In the same manner you can loop over all the unique substrings in lexicographical order. Consider the suffix array, if we look at all substrings that start at the index of the start of the suffix (or sa[i]), the ones that have already be counted are the substrings in the longest common prefix of the current suffix and the suffix before it. This is by the definition of lcp[i]. So you can do pie and get that the amount of unique substrings sa[i] contributes is n — sa[i] — lcp[i] (with some off by 1's of course). A scuffed implementation can be found here.

Substring Order 2

This solution revolves around the fact that the sum of lcp[i] is linear. There are two cases to consider: 1) the answer substring appears more than once in the string, 2) the answer substring does not. For case 2, we can refer to the solution for Substring Order 1, but for case 1, its a little harder. So for some sa[i] we have all the substrings that start at sa[i] and have length <= lcp[i]. Let's try to think about how many times exactly each of these strings appear. Since we have lcp(i, h) >= lcp(i, j) for any i < h < j (and lcp returns the length of the longest common prefix of the two suffixes), we can see that binary search can be done on how many times this substring appears. We start at index sa[i] and binary search on the last time lcp(sa[i], sa[j]) >= the length of the substring we are looking at. What is the complexity of such an algorithm? It is actually still O(nlogn). To avoid double counting, we should only count the contribution of a string if and only if lcp[i] >= lcp[i-1] (since that means its the first time the substring is lexicographically smallest). For the rest of the substrings, we can use the n — sa[i] — lcp[i] trick from Substring Order 1. I am not even going to try and say this implementation is good, but here it is.

Substring Distribution

This problem again relies on the fact that the contribution of unique strings from some suffix sa[i] is n — sa[i] — lcp[i]. So for some given suffix it will contain unique substrings of lengths lcp[i]+1 to sa[i]. Using the psum[l]++, psum[r]-- trick, we have an algorithm with linear complexity with the suffix array construction aside. A scuffed implementation can be found here.

  • »
    »
    2 years ago, # ^ |
      Vote: I like it +13 Vote: I do not like it

    My implementation of your solution of Substring Order II : https://cses.fi/paste/1a1adab9babc8ef84a7c56/
    I guess it might be more readable and easy to understand. (I wasn't able to understand your approach, until I did a dry run on your code. So I am putting my implementation which might be easier to understand)

    • »
      »
      »
      5 months ago, # ^ |
        Vote: I like it 0 Vote: I do not like it

      I did a similar thing but I got TLE for the last case. Can anyone help me optimize this code more? (Substring Order II)

      Code
      • »
        »
        »
        »
        5 months ago, # ^ |
          Vote: I like it 0 Vote: I do not like it

        Well, I got accepted using a different technique. It is similar to Substring Order I, but instead of traversing the suffix array from the first, now it will traverse the suffix array from the last and search for the (Total substring — k-th + 1)-th smallest from the last.

        Prerequisite:

        Range Updates and Sums

        Solution:

        Substring Order II
  • »
    »
    2 years ago, # ^ |
      Vote: I like it +8 Vote: I do not like it

    The idea that the sum max(lcp[i] — lcp[i-1], 0) is linear that you present in the substring order II is wrong. Kasai algorithm for building the lcp array is linear, but it acts on suffixes in decreasing order of size while your algorithm acts on suffixes in lexicographical order. I coded the solution that you described and got AC on CSES, but than I hacked both our solutions with a random string concatenated with itself, which achieves the worst time complexity of O(n²log(n)) (n² different substrings that appear at least twice and we do a binary search on the lcp sparse table for each one).

»
3 years ago, # |
  Vote: I like it +8 Vote: I do not like it

Auto comment: topic has been updated by dutin (previous revision, new revision, compare).

»
3 years ago, # |
  Vote: I like it 0 Vote: I do not like it

I need solution problem special string. Can you help me?

  • »
    »
    3 years ago, # ^ |
      Vote: I like it +3 Vote: I do not like it
    • »
      »
      »
      3 years ago, # ^ |
        Vote: I like it 0 Vote: I do not like it

      Thanks you so much. But it only have code, you can give me tutorial for this problem

      • »
        »
        »
        »
        3 years ago, # ^ |
          Vote: I like it 0 Vote: I do not like it

        This problem can be seen as an extension of prefix sums. Run a prefix sum on the string, keeping track of an array of length 26 which is the number of occurrences of each letter. If the current prefix contains all letters in the string and each letter occurs an equal number of times, it is a valid substring. Because each substring can be represented as a differences of prefixes, we find the number of previous prefixes where the excess elements are in excess the exact same amount.

        For instance, at the current prefix, we have: a: 4 b: 3 c: 2 d: 2 e: 2 ...

        We have to find the number of previous prefixes where a is +2 and b is +1. a: +2 b: +1 c: 0 d: 0 e: 0 ...

        This is where having a map comes in handy. The mentioned solution uses a map of vectors, although an array<int, 26> would probably work better.

    • »
      »
      »
      3 years ago, # ^ |
        Vote: I like it 0 Vote: I do not like it

      :O

»
3 years ago, # |
Rev. 2   Vote: I like it 0 Vote: I do not like it

7. Required Substring:

Solution:
Code:
  • »
    »
    3 years ago, # ^ |
      Vote: I like it 0 Vote: I do not like it

    Thanks for the solution! Do you mind if I attribute this to you and move this up to the main post?

»
3 years ago, # |
  Vote: I like it +9 Vote: I do not like it

All of those are solvable using hashing, except the first one, which has a simpler solution using Trie.

Short

P.S. Maybe except the 7the one also, I'm not sure.

  • »
    »
    2 years ago, # ^ |
      Vote: I like it 0 Vote: I do not like it

    How to solve Finding Periods with hashing?

    • »
      »
      »
      2 years ago, # ^ |
        Vote: I like it +11 Vote: I do not like it

      Using polynomial hashes we can get the hash of any substring in $$$\mathcal{O}(1)$$$ time. We can naively check each prefix, and a prefix of length $$$k$$$ takes time $$$\frac n k$$$ time to check. Thus, the problem can be solved in $$$\mathcal{O}(n \ln n)$$$ time.

      • »
        »
        »
        »
        2 years ago, # ^ |
          Vote: I like it 0 Vote: I do not like it

        I didn't know we could get the hashes in $$$O(1)$$$, thank you.

  • »
    »
    18 months ago, # ^ |
      Vote: I like it 0 Vote: I do not like it

    Is the 17. Substring Distribution solvable by hashing? Naively checking all substrings of lengths from 1 to n — 1 is going to result in $$$O(n ^ 2)$$$ time complexity, unless there is some other way?

  • »
    »
    5 months ago, # ^ |
      Vote: I like it 0 Vote: I do not like it

    Problem 1. Word combinations is also solvable using hashing. But it requires a bit of optimization in the inner loop while calculating DP. I also used pragmas to speed up my code.

    Since max length can go upto 5000 We don't necessarily need to run inner loop 5000 times. Instead run at max(dictonary[i].size()).

    Here's my hashing solution. It took 0.96 seconds to pass.

    Code
    CSES acceped link
    • »
      »
      »
      5 months ago, # ^ |
        Vote: I like it 0 Vote: I do not like it

      (Sorry for necroposting, but this post was already in the recent list and the CSES problemset is fairly popular.)

      It's possible to speed this up further. Let $$$M$$$ be the sum of the lengths of the strings in the dictionary. Then, there can be at most $$$O(\sqrt{M})$$$ distinct lengths in the dictionary. We can maintain a map that maps each distinct length $$$l$$$ to the set of hashes of the strings in the dictionary of length $$$l$$$.

      To compute the transitions, we can check for each possible length $$$l$$$ if the hash of the suffix of length $$$l$$$ is in the corresponding set of hashes. This gives an algorithm with time complexity $$$O(n\sqrt{M}\log k + M + k\log M)$$$. (You can get rid of the stray logs using unordered versions of stl structures.)

      My (not particularly pretty) implementation takes a maximum of 0.1s, without any pragmas.

»
18 months ago, # |
  Vote: I like it 0 Vote: I do not like it

Can you explain why using Z-algorithm can solve Finding Periods? I don't know how to guide to the conclusion that checking i + z[i] >= n is sufficient.

  • »
    »
    18 months ago, # ^ |
      Vote: I like it 0 Vote: I do not like it

    If the suffix starting at $$$i$$$ is equal to the prefix of the string up to $$$i$$$, then $$$s[:i-1]$$$ is a period. You can verify that this works on the sample case ($$$s="abcabca"$$$, $$$z=[0,0,0,4,0,0,1]$$$). I should have mentioned that the string itself is always a period and can be hardcoded.

»
7 months ago, # |
Rev. 2   Vote: I like it 0 Vote: I do not like it

For Substring Distribution, we don't really need BFS. Each node represents unique strings for lengths in range [length(link(v)) + 1, length(v)]. Therefore, it's enough to do for(i) {++ans[st[st[i].link].len + 1], --ans[st[i].len + 1] } Example code: boop

»
5 months ago, # |
  Vote: I like it 0 Vote: I do not like it

Can somebody tell me how can we solve Finding Periods question with KMP algorithm?

»
3 months ago, # |
  Vote: I like it 0 Vote: I do not like it

How to Solve Finding Periods using String hashing or Rabin karp?

»
4 weeks ago, # |
  Vote: I like it 0 Vote: I do not like it

For first one

we can solve it using Trie + DP. I have written simple java code