Word Collection - EDITORIAL:
Author and Editorialist: kishen1912000
DIFFICULTY:
Medium
PROBLEM:
You are given two strings N and W. You need to find the total number of ways to cut off non-zero number of W's from N. Note that when one occurrence of W is cut-off, if there existed an overlapping occurrence originally, then it cannot be cut-off anymore (as part of it is no more present). For example, in ababa, you can cut-off only one occurrence of aba at a time, as they are overlapping. Return the answer modulo 10^9+7.
QUICK EXPLANATION:
Use an efficient pattern matching algorithm (like KMP, etc) to find all the occurrences of W in N. Store this info as a bit array, where a 1 at index i denotes that an occurrence of W ended at index i. Next, using dynamic programming and prefix sums, find the number of ways of picking 1's such that the picked 1's are at least |W| distance apart.
EXPLANATION:
First of all, let us convince ourselves that we need to obtain all the occurrences of W in N. Without this info we cannot proceed to solve this problem. For this purpose, we can use any efficient pattern matching algorithm like Knuth-Morris-Pratt Algorithm (KMP), Z-Algorithm, etc. I will not discuss these algorithms here, but you can find out more about them here.
Let us store the information from the above algorithm in an array A where the value at index i is 1 if an occurrence of W in N ends at index i. Otherwise, the value is 0.
For example, Let N = ababa and W = aba. Then, A = [0,0,1,0,1].
Now, essentially we have reduced our problem to the following problem, what are the total number of ways to pick non-zero number of 1's from A such that the picked 1's are mutually at least |W| distance apart.
This problem can be solved using Dynamic Programming.
Let dp[i] denote the number of ways of picking non-zero number of 1's in the array A[1..i] such that the chosen 1's are mutually at least |W| distance away, and the 1 at A[i] is picked. If A[i]=0, then dp[i]=0.
Also, let prefix[i] = \sum_{j=1}^{i}dp[j]. prefix[i] essentially denotes the number of ways to pick non-zero number of 1's in the array A[1..i] such that the chosen 1's are mutually at least |W| distance apart.
When we are processing index i, if A[i] = 1, that means we pick this 1. So, now we need to find the total number of ways to pick non-zero 1's in the array A[1..i-|W|]. That is nothing but prefix[i-|W|]. Therefore, we get
dp[i] = 1+prefix[i-|W|]
Also, update prefix[i] = prefix[i-1]+dp[i]
The final answer will be prefix[n].
Also, as the values can go huge, appropriate modular arithmetic is to be applied at places.
Further, the space can be improved if we use dp[i] to directly store the prefix[i] value, as dp[i] only depends on prefix[i] value.
Time Complexity:
Time taken for pre-processing (finding A) = \mathcal{O}(|N|+|W|)
Time taken to compute dp = \mathcal{O}(|N|)
Running time: \mathcal{O}(|N|+|W|)
SOLUTIONS:
Setter's Solution
M = 10**9+7
def lps_compute(s):
n = len(s)
lps = [0]*n
curr = 0
lps[0] = 0
i = 1
while i < n:
if s[i]== s[curr]:
curr += 1
lps[i] = curr
i += 1
else:
if curr != 0:
curr = lps[curr-1]
else:
lps[i] = 0
i += 1
return lps
def kmp(s1,s2):
n,m = len(s1),len(s2)
lps = lps_compute(s2)
ans = [0]*n
i,j = 0,0
while i < n:
if s1[i] == s2[j]:
i += 1
j += 1
else:
if j==0:
i+=1
else:
j = lps[j-1]
if j == m:
ans[i-1] = 1
j = lps[j-1]
return ans
for _ in range(int(input())):
s1 = input().strip()
s2 = input().strip()
n,m = len(s1),len(s2)
arr = kmp(s1,s2)
dp = [0]*n
if arr[0] == 1:
dp[0] = 1
for i in range(1,n):
if arr[i]==1:
if i==m-1:
dp[i] = 1
else:
dp[i] = (1 + dp[i-m])%M
dp[i] = (dp[i-1]+dp[i])%M
print(dp[-1])