numero di stringhe, quando ogni carattere deve apparire anche volte


9

Sto colpendo il mio cranio a questo problema da un po 'di tempo e sta davvero iniziando a frustrarmi. Il problema è:

Ho una serie di personaggi, A, B, C, e D. Devo dire in quanti modi può essere costruita una stringa da quei caratteri, quando la lunghezza è ne ogni personaggio deve apparire anche volte.

Ad esempio, la risposta per n = 2è 4:

AA
BB
CC
DD

La risposta per n = 4è 40. Alcune di queste stringhe valide sono:

AAAA
AABB
CACA
DAAD
BCCB

Sono bloccato a elaborare una logica. Sento che potrebbe esserci una soluzione DP per questo. La forzatura brutale attraverso questo è fuori discussione: il numero di soluzioni cresce rapidamente in enormi numeri.

Ho provato a disegnare tutti i tipi di idee su carta, senza risultati. Quasi tutte quelle idee che ho dovuto scartare in quanto la loro complessità era troppo grande. La soluzione dovrebbe essere efficiente per n = 10^4.

Una delle mie idee era di non tenere mai traccia delle stringhe reali, ma solo se ogni personaggio è apparso in tempi pari o dispari. Non sono riuscito a trovare un modo per applicare questa logica.

Qualcuno può aiutarmi?


3
Devi enumerare le stringhe o calcolare il numero di stringhe? Se hai solo bisogno del numero di stringhe, puoi probabilmente usare la combinatoria per calcolare direttamente la quantità.

@Snowman È necessario solo il numero di stringhe possibili. Tuttavia, trovo improbabile che io possa usare la combinatoria qui. Anche se ci fosse un modo, sono sicuro che il problema non dovrebbe essere risolto con la matematica pura, e per questo motivo non lo voglio. O cosa volevi dire?
Olavi Mustanoja,

2
Sicuro che puoi usare la combinatoria. Per una stringa di lunghezza N, ottieni tutte le combinazioni di {AA, BB, CC, DD}. Per ogni combinazione, ottieni le permutazioni uniche. Quindi combinare i risultati per ciascuna combinazione in un set di permutazioni uniche. Non sono sicuro di come farlo, principalmente a causa del vincolo di unicità, ma sono sicuro che c'è un modo.

@Snowman Capisco cosa intendi. Ma ciò non richiederebbe la memorizzazione almeno delle combinazioni? Ottenere il numero di permutazioni uniche lo richiede e il numero di combinazioni cresce molto rapidamente in proporzioni che non è possibile memorizzare.
Olavi Mustanoja,

1
Possibilmente. Non sono abbastanza esperto con la combinatoria per saperlo con certezza. Forse Mathematics.SE ha una domanda simile a questa? Non ho il tempo di scavare in questo momento, ma questo è un problema interessante. Ci penserò e ricontrollerò.

Risposte:


5

Impostato f(n,d)come una funzione che fornisce il numero di permutazioni di (pari) lunghezza nusando dcaratteri distinti (cioè d=4nel tuo caso).

Chiaramente f(0,d) = 1e f(n,1) = 1poiché c'è un solo arrangiamento quando hai un solo personaggio o zero spazi.

Ora il passo di induzione:

Per creare una stringa valida usando i dcaratteri, prendi qualsiasi stringa di lunghezza pari più corta usando i d-1caratteri e rendila adeguata aggiungendo un multiplo pari di questo nuovo carattere. Il numero di arrangiamenti è choose(n,n_newdigits)dovuto al fatto che è possibile scegliere n_newdigitluoghi al di fuori della lunghezza totale della stringa per avere la nuova cifra e il resto viene riempito dalla stringa originale in ordine.

Per implementarlo usando l'ingenua ricorsione in R, ho fatto:

f <- function(n,d)
{
  if(n==0) return(1)
  if(d==1) return(1)
  retval=0
  for (i in seq(from=0, to=n, by=2)) retval=retval+f(n-i,d-1)*choose(n,i)
  return(retval)
}

f(4,4)
# 40    

f(500,4)
# 1.339386e+300 takes about 10 secs

per il tipo di numeri che ti interessano avrei pensato che sarebbe stato più efficiente archiviare i numeri in un array bidimensionale e iterare sull'aumentare di d, ma ciò potrebbe dipendere dalla tua scelta della lingua.


4

La risposta di Miff è decisamente elegante. Dal momento che avevo quasi finito il mio lo fornisco comunque. La cosa buona è che ottengo lo stesso risultato per n = 500 :-)

Sia d il numero di caratteri diversi consentiti, d = 4 nel tuo caso.

Sia n la lunghezza della stringa, alla fine vedrai anche i valori di n.

Sia il numero di caratteri spaiati in una stringa.

Sia N (n, d, u) il numero di stringhe di lunghezza n, costruite da d caratteri diversi e con u caratteri spaiati. Proviamo a calcolare N.

Ci sono alcuni casi angolari da osservare:

u> d oppure u> n => N = 0

u <0 => N = 0

n% 2! = u% 2 => N = 0.

Quando passi da n a n + 1, devi aumentare di 1 o diminuire di 1, quindi abbiamo una ricorsione secondo

N (n, d, u) = f (N (n-1, d, u-1), N (n-1, d, u + 1))

Quanti modi ci sono per ridurti di uno. Questo è facile, perché dobbiamo accoppiare uno dei personaggi non accoppiati, il che lo rende solo tu. Quindi la seconda parte di f leggerà (u + 1) * N (n-1, d, u + 1), con l'avvertenza ovviamente che dobbiamo osservare che N = 0 se u + 1> n-1 ou +1> d.

Una volta capito questo, è facile vedere quale sia la prima parte di f: in quanti modi possiamo aumentarti quando ci sono personaggi spaiati u-1. Bene, dobbiamo scegliere uno dei personaggi (k- (u-1)) che sono accoppiati.

Quindi, tenendo conto di tutti i casi angolari, la formula ricorsiva per N è

N (n, d, u) = (d- (u-1)) * N (n-1, d, u-1) + (u + 1) * N (n-1, d, u + 1)

Non ho intenzione di leggere in http://en.wikipedia.org/wiki/Concrete_Mathematics come risolvere la ricorsione.

Invece ho scritto del codice Java. Ancora un po 'più goffo, come lo è comunque Java a causa della sua verbosità. Ma ho avuto la motivazione di non usare la ricorsione, poiché si interrompe molto presto, almeno in Java, quando lo stack trabocca a 500 o 1000 livelli di annidamento.

Il risultato per n = 500, d = 4 e u = 0 è:

N (500, 4, 0) = 1339385758982834151185531311325002263201756014631917009304687985462938813906170153116497973519619822659493341146941433531483931607115392554498072196838958545795769042788035468026048125208904713757765805163872455056995809556627183222337328039422584942896842901774597806462162357229520744881314972303360

calcolato in 0,2 secondi, a causa della memorizzazione dei risultati intermedi. N (40000,4,0) viene calcolato in meno di 5 secondi. Codice anche qui: http://ideone.com/KvB5Jv

import java.math.BigInteger;

public class EvenPairedString2 {
  private final int nChars;  // d above, number of different chars to use
  private int count = 0;
  private Map<Task,BigInteger> memo = new HashMap<>();

  public EvenPairedString2(int nChars) {
    this.nChars = nChars;
  }
  /*+******************************************************************/
  // encodes for a fixed d the task to compute N(strlen,d,unpaired).  
  private static class Task {
    public final int strlen;
    public final int unpaired;

    Task(int strlen, int unpaired) {
      this.strlen = strlen;
      this.unpaired = unpaired;
    }
    @Override
    public int hashCode() {
      return strlen*117 ^ unpaired;
    }
    @Override
    public boolean equals(Object other) {
      if (!(other instanceof Task)) {
        return false;
      }
      Task t2 = (Task)other;
      return strlen==t2.strlen && unpaired==t2.unpaired;
    }
    @Override
    public String toString() {
      return "("+strlen+","+unpaired+")";
    }
  }
  /*+******************************************************************/
  // return corner case or memorized result or null  
  private BigInteger getMemoed(Task t) {
    if (t.strlen==0 || t.unpaired<0 || t.unpaired>t.strlen || t.unpaired>nChars
        || t.strlen%2 != t.unpaired%2) {
      return BigInteger.valueOf(0);
    }

    if (t.strlen==1) {
      return BigInteger.valueOf(nChars);
    }
    return memo.get(t);
  }

  public int getCount() {
    return count;
  }

  public BigInteger computeNDeep(Task t) {
    List<Task> stack = new ArrayList<Task>();
    BigInteger result = null;
    stack.add(t);

    while (stack.size()>0) {
      count += 1;
      t = stack.remove(stack.size()-1);
      result = getMemoed(t);
      if (result!=null) {
        continue;
      }

      Task t1 = new Task(t.strlen-1, t.unpaired+1);
      BigInteger r1 = getMemoed(t1);
      Task t2 = new Task(t.strlen-1, t.unpaired-1);
      BigInteger r2 = getMemoed(t2);
      if (r1==null) {
        stack.add(t);
        stack.add(t1);
        if (r2==null) {
          stack.add(t2);
        }
        continue;
      }
      if (r2==null) {
        stack.add(t);
        stack.add(t2);
        continue;
      }
      result = compute(t1.unpaired, r1, nChars-t2.unpaired, r2);
      memo.put(t,  result);
    }
    return result;
  }
  private BigInteger compute(int u1, BigInteger r1, int u2, BigInteger r2) {
    r1 = r1.multiply(BigInteger.valueOf(u1));
    r2 = r2.multiply(BigInteger.valueOf(u2));
    return r1.add(r2);
  }
  public static void main(String[] argv) {
    int strlen = Integer.parseInt(argv[0]);
    int nChars = Integer.parseInt(argv[1]);

    EvenPairedString2 eps = new EvenPairedString2(nChars);

    BigInteger result = eps.computeNDeep(new Task(strlen, 0));
    System.out.printf("%d: N(%d, %d, 0) = %d%n", 
                      eps.getCount(), strlen, nChars, 
                      result); 
  }
}

2

Ho provato a trovare una soluzione ma non ci sono riuscito e ho posto la stessa domanda su Mathematics.StackExchange . Grazie a Rus May , ecco una soluzione, in Common Lisp:

(defun solve (n)
  (if (evenp n)
      (/ (+ (expt 4 n) (* 4 (expt 2 n))) 8)
      0))

Restituisce sempre 0 per i valori dispari di n. Per n = 500, ecco l'output con SBCL :

* (time (solve 500))

    Evaluation took:
      0.000 seconds of real time
      0.000000 seconds of total run time (0.000000 user, 0.000000 system)
      100.00% CPU
      51,100 processor cycles
      0 bytes consed

    1339385758982834151185531311325002263201756014631917009304687985462938813906170153116497973519619822659493341146941433531483931607115392554498072196838958545795769042788035468026048125208904713757765805163872455056995809556627183222337328039422584942896842901774597806462162357229520744881314972303360
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.