Riconoscendo la soluzione di @ Hirle , ho ancora pensato di pubblicare i miei.
Mi sono avvicinato da una direzione leggermente diversa. Dopo un po 'di riflessione, il programma più semplice di tutti è stato la chiave:
automa finito deterministico. Questo potrebbe essere risolto con un dfa che cerca le occorrenze di una stringa, come con esso posso calcolare
una matrice di adiacenza che posso usare in seguito. Ho deciso di saltare le banalità e ho appena copiato alcuni codici dalla rete per il
versione deterministica dell'algoritmo di Knuth-Morris-Pratt (l'originale se non deterministico, che non serve ai nostri scopi).
Il codice sottostante deriva da qui , con piccole modifiche:
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.
}
}
Per il pattern ABC
questo genera la seguente matrice 2D, o dfa, se vuoi:
[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]
Qui, ad esempio, la prima riga indica che se il personaggio visto è "A", in base al nostro stato corrente andiamo allo stato indicato dalla riga.
Diciamo che lo stato in cui ci troviamo attualmente è lo stato iniziale (stato 0). Se ora vedessimo il carattere 'A', il prossimo stato ci sarebbe stato detto dal primo elemento della prima riga,
che è 1. Quindi ora il nostro stato è 1. Diciamo che, di nuovo, abbiamo visto il carattere 'A'. Il nostro stato rimarrebbe 1, poiché anche il secondo elemento della prima riga è 1. Bene, ora dì che abbiamo visto
il carattere 'B'. Il nostro stato passerebbe al 2, poiché nel secondo elemento della seconda riga (2a riga) è 2.
In sostanza, il nostro stato sarebbe 3 solo se vedessimo i caratteri ABC
in successione. Le righe D-Z sono tutte zeri, poiché se mai avessimo visto un personaggio diverso da A-C, dobbiamo iniziare
la ricerca dall'inizio (che è A, in questo caso).
Per generalizzare, l'elemento matrix [0][1]
ci dice lo stato in cui dovremmo andare, se il carattere che abbiamo visto era A
e eravamo nello stato 1. Allo stesso modo l'elemento matrice
[6][6]
ci ha detto il nostro prossimo stato se il personaggio che abbiamo visto era G
, e eravamo nello stato 6 (nella matrice sopra ovviamente non abbiamo lo stato 6, dato che lo abbiamo costruito con una stringa di lunghezza 3).
Ad ogni modo, ora abbiamo tutto ciò di cui abbiamo bisogno per costruire la matrice di adiacenza. Senza ulteriori indugi, ecco il mio codice per questo:
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;
Quindi, se stessimo costruendo la matrice di adiacenza da un dfa che era stato creato con la stringa "ABC", la matrice di adiacenza sarebbe simile a questa:
[25, 1, 0, 0]
[24, 1, 1, 0]
[24, 1, 0, 1]
[0 , 0, 0, 26]
Qui paths[i][j]
ci dirà quanti modi ci sono per arrivare allo stato j
dallo stato i
. Ad esempio, ci sono 25 modi per arrivare allo stato 0 dallo stato 0, perché c'è solo
un modo per ottenere un altro stato dallo stato 0. Inoltre, ci sono 26 modi per ottenere l'ultimo stato, se siamo nell'ultimo stato.
Se avessimo costruito tutto finora con la stringa "AA", la matrice di adiacenza sarebbe simile a questa:
[25, 1, 0]
[25, 0, 1]
[0, 0, 26]
Quindi, e adesso? Abbiamo la nostra matrice di adiacenza così potente che ci dice cose interessanti, cosa fare con esso? La teoria dei grafi entra in gioco.
Riferimenti 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
.
Quindi quello che sta dicendo è che se eleviamo la nostra matrice di adiacenza in, diciamo, 3a potenza, allora l'elemento paths[0][k]
ci dice quanti percorsi di lunghezza 3
ci sono dallo stato 0 allo stato k
.
Preparerò un po 'di più per questo: se abbiamo una stringa S
, e con quella stringa possiamo dare come risultato l'ultimo stato del dfa, allora consideriamo S
come OK .
Quindi quello che vogliamo sapere per ottenere la risposta al problema originale, è quante stringhe OK ci sono in stringhe più grandi di lunghezza n
.
Ecco perché aumentiamo la matrice di adiacenza nella n
th di potenza. Ora l'elemento paths[0][k]
, dove k
è la lunghezza della stringa che stiamo cercando, dovrebbe dirci quante stringhe OK ci sono in una stringa più grande di lunghezza n
, che solo
così capita di essere la risposta al nostro problema.
Il mio codice per aumentare una matrice in 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;
}
Sto prendendo un modulo di dimensioni 10^9 + 7
per ottenere risposte affidabili per i grandi input.
Codice completo:
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.
}
}
}