Acknowledging @Hirle's solution, I still figured I'd post my own.
I approached this from a little different direction. After some thinking, the simplest program of all held the key:
deterministic finite automaton. This could be solved with a dfa that searched for occurences of a string, as with it I can compute
an adjacency matrix that I can later use. I decided to skip the trivialities and just copied some code off the net for the
deterministic version of Knuth–Morris–Pratt algorithm (the original one if nondeterministic, which doesn't serve our purposes).
The below code originates from here, with little modifications:
public static int[][] dfa;
// create the DFA from a String
public static void kmp(String pat) {
// build DFA from pattern
int m = pat.length();
dfa = new int[26][m];
dfa[pat.charAt(0) - 'A'][0] = 1;
for (int X = 0, j = 1; j < m; j++) {
for (int c = 0; c < 26; c++)
dfa[c][j] = dfa[c][X]; // Copy mismatch cases.
dfa[pat.charAt(j) - 'A'][j] = j+1; // Set match case.
X = dfa[pat.charAt(j) - 'A'][X]; // Update restart state.
}
}
For the pattern ABC
this generates the following 2D-matrix, or dfa, if you will:
[1, 1, 1]
[0, 2, 0]
[0, 0, 3]
[0, 0, 0]
[0, 0, 0]
[0, 0, 0]
[0, 0, 0]
[0, 0, 0]
[0, 0, 0]
[0, 0, 0]
[0, 0, 0]
[0, 0, 0]
[0, 0, 0]
[0, 0, 0]
[0, 0, 0]
[0, 0, 0]
[0, 0, 0]
[0, 0, 0]
[0, 0, 0]
[0, 0, 0]
[0, 0, 0]
[0, 0, 0]
[0, 0, 0]
[0, 0, 0]
[0, 0, 0]
[0, 0, 0]
Here, for example, the first row means that if the character seen is `A', then depending on our current state we go to the state indicated by the row.
Let's say the state we currently are in is the initial state (state 0). If we now saw the character 'A', the next state would be told to us by the first row's first element,
which is 1. So now our state is 1. Let's say we, again, saw the character 'A'. Our state would remain 1, as the first row's second element is also 1. Alright, now say we saw
the character 'B'. Our state would proceed to 2, since in the B-row's (the second row's) second element is 2.
In essence, our state would be 3 only if we saw the characters ABC
in succession. The rows D-Z are all zeroes, since if we ever saw a character other than A-C, we must start
the search from the beginning (which is A, in this case).
To generalize, the matrix element [0][1]
tells us the state we should go to, if the character we saw was A
and we were in the state 1. Similarly the matrix element
[6][6]
told us our next state if the character we saw was G
, and we were in the state 6 (in the above matrix we of course don't have the state 6, since we built it with a string of length 3).
Anyway, now we have all we need to build the adjacency matrix. Without further ado, here's my code for that:
String s = "ABC"; // The string we built our dfa with, e.g the string we are "searching"
paths = new long[s.length() + 1][s.length() + 1];
for (int k = 0; k < 26; k++)
for (int i = 0; i < dfa[0].length; i++)
paths[i][dfa[k][i]]++;
paths[paths.length - 1][paths.length - 1] = 26;
So if we were building the adjacency matrix from a dfa that was built with the string "ABC", the adjacency matrix would look like this:
[25, 1, 0, 0]
[24, 1, 1, 0]
[24, 1, 0, 1]
[0 , 0, 0, 26]
Here paths[i][j]
would tell us how many ways there are to get to the state j
from the state i
. For example, there are 25 ways to get to state 0 from state 0, because there is only
one way to get to some other state from state 0. Also, there are 26 ways to result in the last state, if we are in the last state.
If we had built everything up so far with the string "AA", the adjacency matrix would look like this:
[25, 1, 0]
[25, 0, 1]
[0, 0, 26]
So, what now? We have our oh-so-mighty adjacency matrix that tells us cool stuff, what to do with it? Graph theory comes into play.
Referencing Wolfram Alpha:
the (u,v)
th element of the k
th power of the adjacency matrix of G
gives the number of paths of length k
between vertices u
and v
.
So what it is saying is, that if we raise our adjacency matrix into, say, 3rd power, then the element paths[0][k]
tells us how many paths of length 3
there are from state 0 to state k
.
I'll prep a little more for this: If we have a string S
, and with that string we can result in the last state of the dfa, then we consider S
to be OK.
So what we want to know to get the answer to the original problem, is how many OK strings there are in bigger strings of length n
.
This is why we raise the adjacency matrix into the n
th power. Now the element paths[0][k]
, where k
is the length of the string we are searching, should tell us how many OK strings there are in a bigger string of length n
, which just
so happens to be the answer to our problem.
My code to raise a matrix into n
th power:
public static long[][] matrixToPower(long[][] a, int p) {
long[][] b = a;
for (int n = 1; n < p; n++)
a = matrixMultiplication(a, b);
return a;
}
public static long[][] matrixMultiplication(long[][] m1, long[][] m2) {
int len = m1.length;
long[][] mResult = new long[len][len];
for(int i = 0; i < len; i++) {
for(int j = 0; j < len; j++) {
for(int k = 0; k < len; k++) {
mResult[i][j] += (m1[i][k] * m2[k][j]) % M;
mResult[i][j] %= M;
}
}
}
return mResult;
}
I'm taking a modulo of size 10^9 + 7
to get reliable answers for big input.
Full code:
import java.util.*;
public class Program {
public static final int M = 1000000007;
public static int[][] dfa;
public static long[][] paths;
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
String s = sc.next();
kmp(s); // generate dfa
paths = new long[s.length() + 1][s.length() + 1];
for (int k = 0; k < 26; k++)
for (int i = 0; i < dfa[0].length; i++)
paths[i][dfa[k][i]]++;
paths[paths.length - 1][paths.length - 1] = 26;
paths = matrixToPower(paths, n);
System.out.println(paths[0][s.length()]);
}
public static long[][] matrixToPower(long[][] a, int p) {
long[][] b = a;
for (int n = 1; n < p; n++)
a = matrixMultiplication(a, b);
return a;
}
public static long[][] matrixMultiplication(long[][] m1, long[][] m2) {
int len = m1.length;
long[][] mResult = new long[len][len];
for(int i = 0; i < len; i++) {
for(int j = 0; j < len; j++) {
for(int k = 0; k < len; k++) {
mResult[i][j] += (m1[i][k] * m2[k][j]) % M;
mResult[i][j] %= M;
}
}
}
return mResult;
}
// create the DFA from a String
public static void kmp(String pat) {
// build DFA from pattern
int m = pat.length();
dfa = new int[26][m];
dfa[pat.charAt(0) - 'A'][0] = 1;
for (int X = 0, j = 1; j < m; j++) {
for (int c = 0; c < 26; c++)
dfa[c][j] = dfa[c][X]; // Copy mismatch cases.
dfa[pat.charAt(j) - 'A'][j] = j+1; // Set match case.
X = dfa[pat.charAt(j) - 'A'][X]; // Update restart state.
}
}
}