Mostrando postagens com marcador Programação Dinâmica. Mostrar todas as postagens
Mostrando postagens com marcador Programação Dinâmica. Mostrar todas as postagens

quarta-feira, 20 de março de 2013

Escalonamento de intervalos com pesos


  • Cada tarefa j tem um início $s_j$ e um fim $f_j$ e tem um peso ou valor $v_j$.
  • Duas tarefas são compatíveis se elas não se sobrepõem 
  • Meta: encontrar o subconjunto de tarefas mutuamente compatíveis com maior peso.



O algoritmo guloso funciona para esse problema quando todas as tarefas tem o mesmo peso.
1. Ordene as tarefas em ordem crescente de tempo final.
2. Adicione um tarefa no subconjunto construído se ela é compatível quando as tarefas previamente escolhidas. Note que para executar este passo basta comparar o início da tarefa com o final da última tarefa escolhida.

Note que esta estratégia não funciona quando os pesos não são iguais.

O algoritmo guloso falha quando ordenamos por tempo final ou ordenamos por peso.


Contra-exemplo 1: ordenando por tempo final
Contra-exemplo 2: ordenando por peso

Programação Dinâmica

Ordene as tarefas pode ordem de tempo final: $f_1$,$f_2$,$f_3$,....
Defina p(j) = maior indíce i < j tal que a tarefa i é compatível com a tarefa j.


typedef struct{
  int s,f,w;
} tarefa;

tarefa t[MAXN];
int p[MAXN];

int order_by_finish(const void *a, const void *b){
  tarefa pa,pb;

  pa = *(tarefa *)a;
  pb = *(tarefa *)b;

  return pa.f - pb.f;
}

Agora, vamos decompor o problema definindo uma estrutura recursiva. A forma de decompor será chamada de escolha binária. A escolha binária também é utilizada no problema da mochila.

OPT(j) = valor da solução ótima do problema consistindo das tarefas 1,2,..,j.

Note que temos que avaliar dois casos:
• Se a tarefa j é escolhida então não podemos usar nenhuma tarefa que  incompatível com j. Logo, p(j) entra em acão. As tarefas {p(j)+1,...,j-1} serão descartadas, o problema fica:
OPT(j) = OPT(p(j)) + v(j)

• Se a tarefa j não é escolhida então o problema é reduzido para 
OPT(j) = OPT(j-1).


Logo, a solução é dada por 
OPT(j) = max(OPT(p(j)) + v(j), OPT(j-1)).


Main

t[0].s = 0;
t[0].f = 0;
t[0].w = 0;

for(i=1;i<=n;i++)
   scanf("%d %d %d",&t[i].s,&t[i].f,&t[i].w);

qsort(t,n+1,sizeof(tarefa), order_by_finish);

for(i=1;i<=n;i++){
   p[i] = 0;
   for(j=i-1;j>=1;j--){
      if(t[j].f <= t[i].s){
          p[i] = j;
          break;
      }
   }
}

opt[0] = 0;
for(j=1;j<=n;j++){
  opt[j] = max(opt[j-1], opt[ p[j]] + t[j].w);     
}
printf("%d\n",opt[n]);

SPOJ:


Referência:




segunda-feira, 18 de março de 2013

Introdução à Programação Dinâmica


Programação dinâmica é uma técnica de resolução de problemas que aparece de muitas formas em várias situações. A programação dinâmica é baseada na aplicação da técnica de divisão e conquista nos problemas nos quais os subproblemas gerados se repetem.  Esta propriedade chamamos de sobreposição de subproblemas. 

Nem sempre é fácil dividir o problema e encontrar os problemas sobrepostos.

Problema do Tijolo

Nós queremos construir uma parede de tijolos com tijolos do tipo 2 x 1, e se o nosso muro deve ter de duas unidades de altura, podemos fazer o nosso muro de várias maneiras, dependendo do comprimento do muro.


De quantas maneiras podemos construir um muro com comprimento 4? E comprimento 5?  Precisamos aplicar a técnica de divisão e conquista para resolver este problema. Primeiramente, podemos observar que o tijolo pode ficar em pé ou dois deitados.

Considerando uma parede inicial com comprimento n, no primeiro caso, temos que cobrir uma parede com comprimento n-1 e no segundo caso uma parede com comprimento n-2.

Se observamos a maneira que uma parede com comprimento 3 é coberta, podemos notar que as duas primeiras maneiras começam com um tijolo em pé depois segue o padrão do comprimento 2 e a última maneira começa com dois tijolos deitados.

Algoritmo

int f(int n){ 
 cont++;
 if(n<=2) return n;
 else return f(n-1)+f(n-2);
}
int main(){
 cont = 0;
 clock_t begin = clock();  
 printf("%d\n",f(40));
 printf("chamadas %d\n",cont);
 double elapsed = ((double) (clock() - begin)) / CLOCKS_PER_SEC;
 printf("EXECUTION TIME(seconds): %8.5g second(s). \n", elapsed);
}

165580141
chamadas 204668309
EXECUTION TIME(seconds):    1.367 second(s).

Observe que existe muita intersecção entre os subproblemas gerados.
f(5)
f(4) + f(3)
f(3)+f(2) + f(2) + f(1)
f(2)+f(1)+ f(2) + f(2) + f(1)

Por exemplo, para resolver f(5) será resolvido uma vez f(4), duas vezes f(3), três vezes f(2) e duas vezes f(1).

Podemos reduzir o número de chamadas aproveitando a própria estrutura recursiva e construindo uma solução bottom-up (de baixo para cima).
int f2(int n){ 
 int f[n+1];
 int i;
 if(n<=2) return n;
 else{
  f[1]  = 1;
  f[2]  = 2;
  for(i=3;i<=n;i++){
   f[i] = f[i-1]+f[i-2];
  }
  return f[n];
 }
}
165580141
EXECUTION TIME(seconds):    0.001 second(s).

Uma técnica que pode ser utilizada para reduzir o número de chamadas desnecessárias é a memorização. A memorização consiste em guardar os valores computados para que eles não precisem ser computados novamente.
int memo[MAXN];
void init(){
 int i;
 for(i=0;i<MAXN;i++) memo[i] = -1;
 memo[1] = 1;
 memo[2] = 2;
}
int f3(int n){
 cont++;
 if( n <= MAXN ){
  if(memo[n]!=-1) return memo[n];
  else{
   memo[n] = f3(n-1) + f3(n-2);
   return memo[n];
  }
 }else{
  return f3(n-1) + f3(n-2);
 }
}
int main(){
 cont = 0;
 clock_t begin = clock();  
 init();
 printf("%d\n",f3(40));
 printf("chamadas %d\n",cont);
 double elapsed = ((double) (clock() - begin)) / CLOCKS_PER_SEC;
 printf("EXECUTION TIME(seconds): %8.5g second(s). \n", elapsed);
 
}

165580141
chamadas 77
EXECUTION TIME(seconds):        0 second(s).



Número de combinações


O número de combinações pode ser obtido pela seguinte fórmula:

C(n,k) = $\frac{n!}{k!(n-k)!}$

C(n,k) representa o número de subconjuntos de tamanho k que podem ser formados a partir de um conjunto com n elementos. Observe que podemos dividir em subproblemas fazendo uma simples observação.

Escolha um elemento x qualquer do conjunto com n elementos. O elemento x pode está ou não no subconjunto com k elementos formados.

Caso 1: x está no subconjunto que está sendo formado. Precisamos escolher k-1 elementos de conjunto com n-1 elementos.
Caso 2: x não está no subconjunto que está sendo formado. Precisamos escolher k elementos de conjunto com n-1 elementos.

C(n,k) = C(n-1,k-1) + C(n-1,k)

int C(int n, int k){
 cont ++;
 if(k==0 || n==k ) return 1;
 else return C(n-1,k-1) + C(n-1,k);
}
int main(){
 cont = 0;
 clock_t begin = clock();  
 printf("%d\n",C(30,10) );
 printf("chamadas %d\n",cont);
 double elapsed = ((double) (clock() - begin)) / CLOCKS_PER_SEC;
 printf("EXECUTION TIME(seconds): %8.5g second(s). \n", elapsed);
}
30045015
chamadas 60090029
EXECUTION TIME(seconds):     0.48 second(s).
Pressione qualquer tecla para continuar. . .

Memorizando
int memo[MAXN][MAXN];
void init(){
 int i,j;
 for(i=0;i<MAXN;i++) 
  for(j=0;j<=i;j++)
    memo[i][j] = -1;
 for(i=0;i<MAXN;i++)
  memo[i][0] = memo[i][i] = 1;  
}


int C(int n, int k){
 cont ++;
 printf("%d %d\n",n,k);
 if(memo[n][k]!=-1) return memo[n][k];
 else {
  memo[n][k] = C(n-1,k-1) + C(n-1,k);
  return memo[n][k];
 }
}
int main(){
 cont = 0;
 clock_t begin = clock();  
 init();
 printf("%d\n",C(30,10) );
 printf("chamadas %d\n",cont);
 double elapsed = ((double) (clock() - begin)) / CLOCKS_PER_SEC;
 printf("EXECUTION TIME(seconds): %8.5g second(s). \n", elapsed);
 system("PAUSE");
}



30045015
chamadas 401
EXECUTION TIME(seconds):    0.001 second(s).
Pressione qualquer tecla para continuar. . .

segunda-feira, 14 de janeiro de 2013

Programação Dinâmica - Soma de Subconjunto (Subset Sum)


Soma de Subconjunto

Problema : Dado um conjunto de n números a[i] que a soma total é igual a M, e para algum K≤M, se existe um subconjunto dos números tais que a soma desse subconjunto dá exatamente K?

Solução 1:
Vamos usar uma tabela unidimensional m[0..M], m[b] indica se b pode ser alcançado.
for(i=0;i<=M;i++) m[i] = 0;
m[0]=1;
for(i=0;i
  for(j=M; j>= a[i]; j--)
    m[j] |= m[j-a[i]]

Observações: A idéia original é usar uma matriz bidimensional m[0..i-1][0..M], onde cada linha depende apenas da linha anterior. Mas utilizando um compressão de estados, podemos utilizar apenas um vetor unidimensional. Mas precisamos escrever o loop j na ordem reversa para evitar confusões.

Variações

Existem várias variações do problema da soma de subconjuntos

Doces para duas crianças: Os valores dos a[i]’s representam os valores dos doces. Nós queremos dividir igualmente os doces entre as duas crianças ou da maneira mais justa. Agora o problema não é encontrar um subconjunto com soma fixa K. Nós queremos encontrar um certo K mais próximo de M/2.
http://br.spoj.pl/problems/TESOURO/
http://www.spoj.pl/problems/FCANDY/


Soma de subconjunto com múltiplos suprimentos: Cada elemento a[i] pode ser usado quantas vezes você precise na soma para alcançar o valor K? Talvez você demore ou não para ver a solução. A solução é apenas inverter a direção do loop j na solução j.

Troco (coin change): Agora cada a[i] representam moedas, você quer passar um troco de exatamente K. Talvez existam várias maneiras de você fazer isso, então você quer minimizar (ou maximiza) o número de moedas que você precisa usar. A estrutura da solução não precisa ser alterada, nós precisamos apenas mudar o significado do vetor m. Agora m[b] não será 0 ou 1, será exatamente o número mínimo de moedas que você precisa para alcançar b.
https://br.spoj.pl/problems/MOEDAS/
http://www.spoj.pl/problems/NOCHANGE/


Doces para três crianças: Nós queremos agora dividir os doces mais equilibradamente o possível entre as três crianças. A questão é o que representa “mais equilibradamente o possível”. A solução pode ser feita de diferentes maneiras, mas a estrutura da solução são quase as mesmas. Use um vetor bidimensional m [][], m[b][c] indica se podemos ou não dividir os doces de maneira que b doces vão para a primeira criança, c doces para a segunda criança e o restante vai para a terceira criança.

Contando o troco (couting change): Os a[i]s continuam representando moedas, agora você quer saber quantas maneiras você pode passar o troco K. O número de maneiras de trocar uma quantidade A usando N tipos de moedas é igual a:
1. O número de maneiras de trocar a quantidade A usando todas as moedas anteriores, +
2. O número de maneiras de trocar a quantidade A-D usando todos os N tipos de moedas, onde D é o valor da n enésima moeda.

A árvore de recursão do processo vai reduzir gradualmente o valor de A, então podemos usar estas regras, para determinar o número de maneiras de trocar um certo valor
1. Se A == 0, então vamos contar 1 maneiras.
2. Se A <=0, então vamos contar 0 maneiras
3. Se N tipos de moedas == 0, então vamos contar 0 maneiras.

int coin[] = {1,5,10,25,50};
int count[MAX_MONEY+1];
memset(count,0,sizeof(count));
count[0] = 1;
for (int i=0;i
for (int j=coin[i];j<=MAX_MONEY;++j)
count[j] += count[j-coin[i]];

Problema
http://br.spoj.pl/problems/SEQUENCI/

terça-feira, 13 de novembro de 2012

Programação Dinâmica - Maior Subsequência Comum

O problema da maior subsequência comum (longest commom subsequence) (LCS) é o problema de encontrar a maior subsequência comum a duas sequências X e Y. Este é um problema clássico para
compação de arquivos e bioinformática.

Uma subsequência de uma sequência X de caracteres  pode ser obtida removendo alguns caracteres dessa sequência. Por exemplo,

Considere a seguinte sequência X = ABCBDAB, podemos obter  2^|X| subsequência  distintas de X. Por exemplo, BCBDAB e ACDAB são uma subsequência de X.
#include <stdio.h>
#include <string.h>
#include <algorithm>
#define MAX 100
using namespace std;
int main()
{

    char X [MAX];
    char temp[MAX];
    int m;

    scanf("%s",X);

    m = strlen(X);

    int i,j,index;

    for(i = 0; i < 1<<m ; i++){
        index = 0;
        for(j=0; j<m; j++){
            if( (i & (1<<j)) != 0 ){
              temp[index++] = X[j];
            }
        }
        temp[index] = '\0';
        printf("%s\n",temp);
    }

    return 0;
}
Entrada
BCBD
Saída
B
C
BC
B
BB
CB
BCB
D
BD
CD
BCD
BD
BBD
CBD
BCBD


A solução força bruta consiste em comparar cada subsequencia de X com os símbolos de Y.

Se |X| = m, |Y| = n ,  $2^m$ subsequência de X então a solução força bruta é O(n$2^m$).

Algoritmo Força Bruta
#include <stdio.h>
#include <string.h>
#include <algorithm>
#define MAX 100

using namespace std;

int main()
{

    char X [MAX];
    char Y [MAX];
    char temp[MAX];
    char sol[MAX];
    int m;
    int n;
    int maior, cont;
    int index;

    scanf("%s %s",X,Y);

    m = strlen(X);
    n = strlen(Y);

    int i,j;
    maior = 0;

    for(i = 0; i < 1<<m ; i++){
        index = 0;
        for(j=0; j<m; j++){
            if( (i & (1<<j)) != 0 ){
              temp[index++] = X[j];
            }
        }
        temp[index] = '\0';
        printf("%s\n",temp);

        cont = 0;
        for(j=0;j<n && temp[cont]!='\0';j++){
          if(Y[j] == temp[cont]){
            cont++;
          }
        }

        if(temp[cont]=='\0' && cont > maior){
          maior = cont;
          strcpy(sol,temp);
        }

    }


    printf("%d\n",maior);
    printf("%s\n",sol);



    return 0;
}
Entrada
ABCBDAB BDCABA 
Saída
4 
BCBA

Visualmente, a solução ótima pode ser vista da seguinte maneira:

AB-C-BDAB 
 | | | |
-BDCAB-A- 

Para diminuir a complexidade para resolver este problema, primeiramente, precisamos encontrar uma maneira de decompor o problema grande em termos de subproblemas menores de tal maneira que a solução ótima do problema maior dependa da solução ótima de subproblemas menores. Essa propriedade chamamos de subestrutura ótima.

Definição do problema c[i,j] = problema de encontrar a maior subsequencia comum das seguintes sequência X[1..i] e Y[1..j]. Essas duas sequências são sequências prefixos das sequências inicias.

Subsestrutura Ótima
Caso Base:

  • Se i=0 ou j=0 então c[i,j] = 0. Se umas das sequências é nula então a maior sequência comum também é nula.


  • Se X[i]=X[j] então c[i,j] = c[i-1,j-1] + 1. Se o último caracter for igual nas duas sequências então este caractere está na maior subsequencia comum e podemos reduzir o problema para c[i-1,j-1].


  • Se X[i]!=X[j] então c[i,j] = max(c[i,j-1],c[i-1,j]). Se o último caracter não for igual nas duas sequências então temos duas opções: 
    • deletamos o último caractere da sequência Y e comparamos com a sequência X completa(c[i,j-1])  
    • deletamos o último caractere da sequência X e comparamos com a sequência Y completa(c[i-1,j])
          A solução ótima será dada pelo máximo entre os dois subproblemas.

Algoritmo programação dinâmica

    int n,m;
    int i,j;

    scanf("%s",X);
    scanf("%s",Y);
    
    n = strlen(X);
    m = strlen(Y);

    for(i=0;i<=n;i++)
      c[i][0] = 0;

    for(j=0;j<=m;j++)
      c[0][j] = 0;


    for(i=1;i<=n;i++)
      for(j=1;j<=m;j++)
        if(X[i-1]==Y[j-1])
          c[i][j] = c[i-1][j-1] + 1;
        else
          c[i][j] = max( c[i-1][j] , c[i][j-1]);



    printf("%d\n",c[n][m]);

Entrada
ABCBDAB BDCABA
Saída
 4

A tabela com todos os subproblemas obtida é:

c
B D C A B A

0 0 0 0 0 0 0
A 0 0 0 0 1 1 1
B 0 1 1 1 1 2 2
C 0 1 1 2 2 2 2
B 0 1 1 2 2 3 3
D 0 1 2 2 2 3 3
A 0 1 2 2 3 3 4
B 0 1 2 2 3 4 4


Consultando a tabela, podemos descobrir a solução do problema lcs('AB','BD') = 1 (vermelho) ou do problema lcs('ABCB','BDCAB') = 3 (azul).

A maior subsequência comum pode ser extraída da tabela da seguinte maneira:
i = n;
j = m;

stack <char> pilha;

while(i!=0 && j!=0){
   if(X[i-1]==Y[j-1]){
      pilha.push(X[i-1]);
      i--;
      j--;
    }else if(c[i-1][j] > c[i][j-1]){
      i--;
    }else{
      j--;
    }
}

while( !pilha.empty() ){
    cout << pilha.top() ;
    pilha.pop();
}
cout << endl;

A maior subsequência comum pode ser obtida recursivamente da seguinte maneira:
void lcs(int i,int j){
  if(i!=0 && j!=0){
      if(X[i-1]==Y[j-1]){
        lcs(i-1,j-1);
        printf("%c",X[i-1]);
      }else if(c[i-1][j] > c[i][j-1]){
        lcs(i-1,j);
      }else{
        lcs(i,j-1);
      }
  }
}

Chamada
lcs(n,m);
cout << endl;

Para calcular a solução de um subproblema em particular, precisamos apenas de duas linhas para obter a solução dos subproblemas c[i-1,j], c[i,j-1], c[i-1,j-1]. Podemos otimizar a utilização da memória aproveitando este fato da seguinte maneira:
char X[MAXN];
char Y[MAXN];
int c[2][MAXN];

int n,m;
int i,j;

scanf("%s",X);
scanf("%s",Y);

n = strlen(X);
m = strlen(Y);

for(j=0;j<=m;j++)
  c[0][j] = 0;

for(i=1;i<=n;i++)
  for(j=1;j<=m;j++)
    if(X[i-1]==Y[j-1])
      c[i&1][j] = c[(i-1)&1][j-1] + 1;
    else
       c[i&1][j] = max(c[(i-1)&1][j] , c[i&1][j-1]);

printf("%d\n",c[n&1][m]);
Este problema tem variações interessantes:
  • Encontrar a menor supersequência comum entre duas string. Por exemplo, a menor supersequência comum entre AAATTT e GAATCT é GAAATCTT. Observe que AAATTT e GAATCT são subsequência de GAAATCTT e GAAATCTT é a menor sequência com essa propriedade.Esse problema foi explorado em: http://br.spoj.pl/problems/PARQUE/
  •  Encontrar a menor distância entre duas strings A e B. A distância entre duas string é dada pelo menor número de operações necessária para transformar A em B. As operações permitidas são deletar um caractere, adicionar um caractere ou substituir um caractere por outro. Pode ser encontrado aqui: http://www.spoj.pl/problems/EDIST/
Links interessantes:

sexta-feira, 7 de setembro de 2012

Programação Dinâmica - Algoritmo de Kadane 2D

Um extensão interessante do problema de encontrar um segmento de soma máxima em um vetor é encontrar uma submatriz de soma máxima em uma matriz a. O algoritmo mais ou menos trivial para esse problema seria O($n^4$).  Primeiramente, vamos construir uma matriz temporária temp.

temp[i][j] = $\sum a[k][l]$ , 1<=k<=i e 1<=l<=j

Podemos calcular temp de uma maneira inteligente fazendo as seguintes observações:
Usando o principio da inclusão-exclusão, temos o seguinte:

$temp[i,j] = temp[i-1,j] + temp[i,j-1] - temp[i-1,j-1] + a[i,j]$

Com a tabela temp calculada, podemos encontrar a soma de uma submatriz que está em cinza rapidamente da seguinte maneira:

$soma = temp[k,l] - temp[k,j-1] - temp[i-1,l] + temp[i-1,j-1]$

Considere a seguinte matriz a:

1
2
-1
-3
-1
4
1
-5
2


A matriz temp calculada seria:

0
0
0
0
0
1
3
2
0
-2
-1
2
0
-1
-5
0




O algoritmo trivial consiste em variar todas as possibilidade i,j,k e l.
void seg_max2d( int **a, int n,int m, int & x1,int & y1 , int & x2, int & y2, int & max){
 int i,j,k,l;
 int temp[n+1][m+1];
 int soma;
 
 for(i=0;i<=n;i++) temp[i][0] = 0;
 for(j=0;j<=m;j++) temp[0][j] = 0;
 
 for(i=1;i<=n;i++)
  for(j=1;j<=m;j++)
   temp[i][j] = temp[i-1][j] + temp[i][j-1] - temp[i-1][j-1] + a[i-1][j-1];
 
 max = -1;   
 for(i=1;i<=n;i++)
  for(j=1;j<=m;j++)
   for(k=i;k<=n;k++)
    for(l=j;l<=m;l++){
     soma = temp[k][l] - temp[i-1][l] - temp[k][j-1] + temp[i-1][j-1];
     if(soma > max){
      max = soma; 
      x1 = i;
      y1 = j;
      x2 = k;
      y2 = l;
     }
    }   
}

No exemplo acima, a soma máxima é 6 e a submatriz é a[2..3,3..3].

A complexidade desse algoritmo é O($n^4$). Podemos aplicar o algoritmo de kadane1D para reduzir a complexidade do algoritmo para O($n^3$).

A idéia do algoritmo kadane2D é aplicar o algoritmo de kadane1D nos seguintes vetores:


Considere a seguinte matriz a:

1
2
-1
-3
-1
4
1
-5
2

1° vetor = 1° linha
1 2 -1 = segmento de soma máxima [1 2]  soma 3
2° vetor = 1° linha + 2° linha
-2 1 3 = segmento de soma máxima [1 3] soma 4
3° vetor = 1° linha + 2° linha + 3° linha
1 -4 5 = segmento de soma máxima [5] soma 5
4° vetor = 2° linha
-3 -1 4 = segmento de soma máxima [4] soma 4
5° vetor = 2° linha + 3° linha
2 -6 6  = segmento de soma máxima [6] soma 6
6° vetor = ° linha
1 -5 2 = segmento de soma máxima [2] soma 2

void kadane2d( int **a, int n,int m, int & x1,int & y1 , int & x2, int & y2, int & max_sum){
  
   int i,j,k;
   int tmp[m];
   int fy1,fy2;
   int cur;
   
   for (i=0; i< n; i++)
   {
       for(k=0; k<m; k++)
           tmp[k] = 0;

       for (j=i; j<n; j++)
       {
           for(k=0; k<m; k++)
               tmp[k] += a[j][k];
           
           kadane(tmp, m, fy1, fy2, cur);

           if (cur > max_sum)
           {
               y1 = fy1;
               y2 = fy2;
               x1 = i;
               x2 = j;
               max_sum = cur;
           }
       }
   } 

}












Programação Dinâmica - Algoritmo de Kadane


O problema de segmento de soma máxima é a tarefa de encontrar um subvetor com a maior soma possivel. Por exemplo, para a seguinte sequência de valores -2,1,-3,4,-1,2,1,-5,4; o segmento de soma máxima é 4,-1,2,1 com soma 6. O algoritmo trivial seria esse:

void seg_max(int *v, int n, int & x, int &y , int & max){
 int i,j;
 int soma; 
 max = -1; 
 for(i=0;i<n;i++){
  soma = 0;
  for(j=i;j<n;j++){
   soma += v[j];
   if( soma > max ){
    max = soma;
    x = i;
    y = j;
   }
  }
 }
}

int main(){
int x,y,max;
int v[] = {-2,1,-3,4,-1,2,1,-5,4};
seg_max(v,9,x,y,max);
printf("segmento de soma maxima [%d-%d] com soma %d\n", x,y,max);
}




Saída
segmento de soma maxima [3-6] com soma 6

O algoritmo acima tem complexidade O(n*n). Em 1977, Jay Kadane desenvolveu um algoritmo linear para resolver este problema. A idéia do algoritmo é simples, basta atualizar duas variáveis o max_atual e o max_total; A regra para atualizar o max_atual é:

max_atual = max( max_atual + v[i] , v[i]). 

A regra para atualizar o max_total é:

max_total = max( max_total , max_atual )

A ideia é incrementar o max_atual com o próximo valor da sequência enquanto o valor de max_atual não fica negativo. 


void kadane(int *v, int n, int & x, int &y , int & max_total){
 int max_atual;
 int xtemp;
 int i;
 max_atual = 0;
 max_total = -1;
 xtemp = 0;
 for(i=0;i<n;i++){
  max_atual = max_atual + v[i];
  if(max_atual < 0) { 
   max_atual = 0;
   xtemp = i+1;
  }
  if(max_atual > max_total){
   max_total = max_atual;
   x = xtemp;
   y = i;
  } 
 }
}
int main(){
 int x,y,max;
 int v[] = {-2,1,-3,4,-1,2,1,-5,4};
 kadane(v,9,x,y,max);
 printf("segmento de soma maxima [%d-%d] com soma %d\n", x,y,max);
}

Saída
segmento de soma maxima [3-6] com soma 6


Teste esses algoritmo nos seguintes problemas:
http://br.spoj.pl/problems/BAPOSTAS/ 
http://br.spoj.pl/problems/SALDO/ 








sábado, 26 de maio de 2012

Programação Dinâmica



·         Subestrutura ótima
Uma solução ótima para o problema contém em seu interior soluções ótimas para subproblemas. Constrói-se uma solução ótima para um problema a partir de soluções ótimas para subproblemas.
Exemplo: todo subcaminho de um caminho ótimo também é ótimo.
·         Superposição de problema
Ocorre quando um algoritmo recursivo reexamina o mesmo problema inúmeras vezes.
Exemplo: Sequência de Fibonacci
F(0) = 0
F(1) = 1
F(n) = F(n-1) + F(n-2)










Este problema tem subestrutura ótima?
Este problema tem superposição de subproblemas?
Eu preciso calcular todos os caminhos distintos para encontrar o menor caminho?
O menor caminho para chegar em um vértice depende do menor caminho previamente calculado de quais outros vértices?