• Nenhum resultado encontrado

CIC 110 Análise e Projeto de Algoritmos I

N/A
N/A
Protected

Academic year: 2022

Share "CIC 110 Análise e Projeto de Algoritmos I"

Copied!
61
0
0

Texto

(1)

CIC 110

Análise e Projeto de Análise e Projeto de

Algoritmos I Algoritmos I

Universidade Federal de Itajubá

Prof. Roberto Affonso da Costa Junior

(2)

AULA 10 AULA 10

– Manipulação de bits

(3)

Manipulação de bits Manipulação de bits

Todos os dados em programas de computador são armazenados internamente como bits, isto é, como números 0 e 1. Este capítulo discute a representação de bits de números inteiros e mostra exemplos de como usar operações de bit. Acontece que há muitos usos para a manipulação de bits na programação de algoritmos.

(4)

Representação de bits Representação de bits

Na programação, um inteiro de n bits é armazenado internamente como um número binário que consiste em n bits. Por exemplo, o int do tipo C++ é um tipo de 32 bits, o que significa que cada número int é composto de 32 bits.

Aqui está a representação de bits de int do número 43:

00000000000000000000000000101011

(5)

Representação de bits Representação de bits

Os bits na representação são indexados da direita para a esquerda. Para converter uma representação de bits bk…b2b1b0 em um número, podemos usar a fórmula:

Por exemplo:

bk2k + + b222 + b121 + b020

1 x 25 + 1 x 23 + 1 x 21 + 1 x 20 = 43

(6)

Representação de bits Representação de bits

A representação de bits de um número é com sinal ou sem sinal. Geralmente, uma representação com sinal é usada, o que significa que números negativos e positivos podem ser representados. Uma variável com sinal de n bits pode conter qualquer número inteiro entre -2n-1 e 2n-1 - 1. Por exemplo, o tipo int em C++ é um tipo com sinal, então uma variável int pode conter qualquer número inteiro entre -231 e 231 - 1.

(7)

Representação de bits Representação de bits

O primeiro bit em uma representação com sinal é o sinal do número (0 para números não negativos e 1 para números negativos) e os restantes n-1 bits contêm a magnitude do número. O complemento de dois é usado, o que significa que o número oposto de um número é calculado pela primeira inversão de todos os bits do número e, em seguida, aumentando o número por um.

Por exemplo, a representação de bits do número int - 43 é

11111111111111111111111111010101.

(8)

Representação de bits Representação de bits

Em uma representação sem sinal, apenas números não negativos podem ser usados, mas o limite superior para os valores é maior. Uma variável sem sinal de n bits pode conter qualquer número inteiro entre 0 e 2n-1. Por exemplo, em C++, uma variável int sem sinal pode conter qualquer número inteiro entre 0 e 232-1.

Existe uma conexão entre as representações: um número com sinal -x é igual a um número sem sinal 2n - x. Por exemplo, o código a seguir mostra que o número com sinal x = -43 é igual ao número sem sinal y = 232 - 43:

(9)

Representação de bits Representação de bits

Se um número for maior do que o limite superior da representação de bits, o número irá transbordar. Em uma representação com sinal, o próximo número após 2n-1 - 1 é – 2n-1, e em uma representação sem sinal, o próximo número após 2n-1 é 0. Por exemplo, considere o seguinte código:

int x = -43;

unsigned int y = x;

printf("x << %d\n", x); // -43

printf("y << %d\n", y);; // 4294967253

int x = 2147483647

printf("x << %d\n", x); // 2147483647 x++;

printf("x << %d\n", x); // -2147483648

(10)

Representação de bits Representação de bits

Inicialmente, o valor de x é 231 - 1. Este é o maior valor que pode ser armazenado em uma variável int, então o próximo número após 231 - 1 é -231.

(11)

Operações de bits Operações de bits

Operação AND

A operação x & y produz um número que tem um bit em posições onde x e y têm um bits. Por exemplo, 22

& 26 = 18, porque:

Usando a operação &, podemos verificar se um número x é zero, se x & 1 = 0, e x é um, se x & 1 = 1.

Mais geralmente, x é divisível por 2k exatamente quando x & (2k - 1) = 0.

10110 (22)

& 11010 (26)

= 10010 (18)

(12)

Operações de bits Operações de bits

Operação OR

A operação x | y produz um número que tem um bit em posições onde x e y têm um bits. Por exemplo, 22 | 26 = 30, porque:

10110 (22)

| 11010 (26)

= 11110 (30)

(13)

Operações de bits Operações de bits

Operação XOR

A operação x ^ y produz um número que tem um bit em posições onde x e y têm um bits. Por exemplo, 22

^ 26 = 12, porque:

10110 (22)

^ 11010 (26)

= 01100 (12)

(14)

Operações de bits Operações de bits

Operação NOT

A operação NOT ~x produz um número onde todos os bits de x foram invertidos. A fórmula ~x = - x - 1 é válida, por exemplo, ~29 = - 30.

O resultado da operação NOT no nível de bit depende do comprimento da representação de bits, pois a operação inverte todos os bits. Por exemplo, se os números são números int 32-bit, o resultado é o seguinte:

x = 29 00000000000000000000000000011101

~ x = − 30 11111111111111111111111111100010

(15)

Operações de bits Operações de bits

Bit shifts

A mudança de bit esquerda x << k acrescenta k bits zero ao número e a mudança de bit direita x >> k remove os últimos k bits do número. Por exemplo, 14

<< 2 = 56, porque 14 e 56 correspondem a 1110 e 111000. Da mesma forma, 49 >> 3 = 6, pois 49 e 6 correspondem a 110001 e 110.

Note que x << k corresponde a multiplicar x por 2k, e x >> k corresponde a dividir x por 2k arredondado para um número inteiro.

(16)

Aplicações Aplicações

Um número da forma 1 << k tem um bit na posição k e todos os outros bits são zero, então podemos usar esses números para acessar um único bit do número.

Em particular, o k bit de um número é um exatamente quando x & (1 << k) e caso contrário é zero. O código a seguir imprime a representação de bits de um número int x:

for (int i = 31; i >= 0; i--) { if (x&(1<<i)) printf("1\n");

else printf("0\n");

}

(17)

Aplicações Aplicações

Também é possível modificar um único bit de números usando ideias semelhantes. Por exemplo, a fórmula x | (1 << k) define que o k bit de x vale um, a fórmula x & ~ (1 << k) define que o k bit de x vale zero e a fórmula x ^ (1 << k) inverte o k bit de x.

A fórmula x & (x - 1) define que o último bit de x vale zero e a fórmula x & - x define todos os bits são zero, exceto o último bit. A fórmula x | (x - 1) inverte todos os bits após o último bit. Observe também que um número positivo x é uma potência de dois exatamente quando x & (x - 1) = 0.

(18)

Funções adicionais Funções adicionais

O compilador g++ fornece as seguintes funções para contar bits:

__builtin_clz(x): o número de zeros no inicio do bit;

__builtin_ctz(x): o número de zeros no final do bit;

__builtin_popcount(x): o número de um no bits;

__builtin_parity(x): a paridade (par ou ímpar) do número.

As funções podem ser usadas da seguinte forma:

(19)

Funções adicionais Funções adicionais

#include <bits/stdc++.h>

using namespace std;

int main() {

int x = 5328;

printf("Quantidade de zeros antes do primeiro 1: %d\n", __builtin_clz(x));

printf("Quantidade de zeros depois do ultimo 1: %d\n", __builtin_ctz(x));

printf("Quantidade de uns no número: %d\n", __builtin_popcount(x));

printf("A paridade (par ou ímpar) do número: %d\n", __builtin_parity(x));

return 0;

}

Quantidade de zeros antes do primeiro 1: 19 Quantidade de zeros depois do ultimo 1: 4 Quantidade de uns no número: 5

A paridade (par ou ímpar) do número: 1

(20)

Funções adicionais Funções adicionais

#include <bits/stdc++.h>

using namespace std;

int main() {

int x = -5328;

printf("Quantidade de zeros antes do primeiro 1: %d\n", __builtin_clz(x));

printf("Quantidade de zeros depois do ultimo 1: %d\n", __builtin_ctz(x));

printf("Quantidade de uns no número: %d\n", __builtin_popcount(x));

printf("A paridade (par ou ímpar) do número: %d\n", __builtin_parity(x));

return 0;

}

Quantidade de zeros antes do primeiro 1: 0 Quantidade de zeros depois do ultimo 1: 4 Quantidade de uns no número: 24

A paridade (par ou ímpar) do número: 0

(21)

Funções adicionais Funções adicionais

Enquanto as funções anteriores apenas suportam números int, também há versões long long das funções disponíveis com o sufixo ll.

(22)

Representando Conjuntos Representando Conjuntos

Cada subconjunto de um conjunto {0, 1, 2, …, n - 1}

pode ser representado como um inteiro de n bits cujos bits indicam quais elementos pertencem ao subconjunto. Esta é uma maneira eficiente de representar conjuntos, porque cada elemento requer apenas um bit de memória e as operações de conjunto podem ser implementadas como operações de bits.

Por exemplo, dado um tipo int de 32 bits, um número int pode representar qualquer subconjunto do conjunto {0, 1, 2, …, 31}. A representação de bits do conjunto {1, 3, 4, 8} é

que corresponde ao número 28 + 24 + 23 + 21 = 282.

00000000000000000000000100011010,

(23)

Implementação do Conjunto Implementação do Conjunto

O código a seguir declara uma variável int x que pode conter um subconjunto de {0, 1, 2, …, 31}.

Depois disso, o código adiciona os elementos 1, 3, 4 e 8 ao conjunto e imprime o tamanho do conjunto.

x = 0;

x |= (1<<1);

x |= (1<<3);

x |= (1<<4);

x |= (1<<8);

printf("%d -- %d\n", x, __builtin_popcount(x));

(24)

Implementação do Conjunto Implementação do Conjunto

Em seguida, o código a seguir imprime todos os elementos que pertencem ao conjunto:

for (int i = 0; i < 32; i++) {

if (x & (1<<i)) printf("%d ",i);

}

printf("\n");

(25)

Operações de Conjuntos Operações de Conjuntos

As operações de conjunto podem ser implementadas da seguinte forma como operações de bits:

Sintaxe de conjunto Sintaxe de bit Interseção

União

Complemento Diferença

a ∩ b a b a \ bā

a & b a | b a & (~ b)~ a

(26)

Operações de Conjuntos Operações de Conjuntos

Por exemplo, o código a seguir constrói primeiro os conjuntos x = {1, 3, 4, 8} e y = {3, 6, 8, 9}, e então constrói o conjunto z = x y = {1, 3, 4, 6, 8, 9}:∪

int x = (1<<1)+(1<<3)+(1<<4)+(1<<8);

int y = (1<<3)+(1<<6)+(1<<8)+(1<<9);

int z = x|y;

for (int i = 0; i < 32; i++) {

if (z & (1<<i)) printf("%d ",i);

}

printf("\n");

(27)

Iterando através de Iterando através de

Subconjuntos Subconjuntos

O código a seguir passa pelos subconjuntos de {0, 1,

…, n - 1}:

for (int b = 0; b < (1<<n); b++) {

// process subset b }

(28)

Iterando através de Iterando através de

Subconjuntos Subconjuntos

O código a seguir passa pelos subconjuntos com exatamente k elementos:

for (int b = 0; b < (1<<n); b++) {

if (__builtin_popcount(b) == k) {

// process subset b }

}

(29)

Iterando através de Iterando através de

Subconjuntos Subconjuntos

O código a seguir passa pelos subconjuntos com exatamente k elementos:

for (int b = 0; b < (1<<n); b++) {

if (__builtin_popcount(b) == k) {

// process subset b }

}

(30)

Iterando através de Iterando através de

Subconjuntos Subconjuntos

O código a seguir passa pelos subconjuntos de um conjunto x:

int b = 0;

do {

// process subset b } while (b=(b-x)&x);

(31)

Otimizações de bits Otimizações de bits

Muitos algoritmos podem ser otimizados usando operações de bits. Tais otimizações não alteram a complexidade do tempo do algoritmo, mas podem ter um grande impacto no tempo real de execução do código. Nesta seção, discutimos exemplos de tais situações.

(32)

Distâncias de Hamming Distâncias de Hamming

A distância Hamming entre dois pontos a e b de igual comprimento é o número de posições em que as strings diferem. Por exemplo,

Considere o seguinte problema: Dada uma lista de n strings de bits, cada um de comprimento k, calcule a distância Hamming mínima entre duas strings na lista. Por exemplo, a resposta para [00111, 01101, 11110] é 2, porque:

hamming (01101, 11001) = 2.

hamming (00111, 01101) = 2 Hamming (00111, 11110) = 3 hamming (01101, 11110) = 3

(33)

Distâncias de Hamming Distâncias de Hamming

Uma maneira direta de resolver o problema é passar por todos os pares de string e calcular suas distâncias de Hamming, que produz um algoritmo de tempo O (n2k). A seguinte função pode ser usada para calcular distâncias:

int hamming(string a, string b) {

int d = 0;

for (int i = 0; i < k; i++) {

if (a[i] != b[i]) d++;

}

return d;

}

(34)

Distâncias de Hamming Distâncias de Hamming

No entanto, se k for pequeno, podemos otimizar o código armazenando as cadeias de bits como números inteiros e calculando as distâncias de Hamming usando operações de bits. Em particular, se k ≤ 32, podemos simplesmente armazenar as strings como valores int e usar a seguinte função para calcular as distâncias:

int hamming(int a, int b) {

return __builtin_popcount(a^b);

}

(35)

Distâncias de Hamming Distâncias de Hamming

Na função acima, a operação xor constrói uma sequência de bits que tem um bit nas posições em que a e b diferem. Então, o número de bits é calculado usando a função __builtin_popcount.

Para comparar as implementações, geramos uma lista de 10000 strings de bits aleatórias de comprimento 30. Usando a primeira abordagem, a pesquisa levou 13,5 segundos e, após a otimização de bits, demorou apenas 0,5 segundos. Assim, o código otimizado foi quase 30 vezes mais rápido do que o código original.

(36)

Contagem de Subgrid Contagem de Subgrid

Como outro exemplo, considere o seguinte problema:

Dada uma grid n × n cujo quadrado seja preto (1) ou branco (0), calcule o número de subgrupos cujos cantos são pretos. Por exemplo, a grade:

(37)

Contagem de Subgrid Contagem de Subgrid

contém duas soluções de subgrids:

(38)

Contagem de Subgrid Contagem de Subgrid

Existe um algoritmo de tempo O(n3) para resolver o problema: passar por todos os pares de linhas O(n2) e para cada par (a, b) calcular o número de colunas que contêm um quadrado preto em ambas as linhas em tempo O(n). O código a seguir pressupõe que a cor[y][x] denota a cor na linha y e a coluna x:

int count = 0;

for (int i = 0; i < n; i++) {

if (color[a][i] == 1 && color[b][i] == 1) count++;

}

(39)

Contagem de Subgrid Contagem de Subgrid

Então, essas colunas contam contagem (count - 1) / 2 subgrids com cantos pretos, porque podemos escolher dois deles para formar uma subgrid.

Para otimizar este algoritmo, dividimos a grade em blocos de colunas, de modo que cada bloco consiste de N colunas consecutivas. Então, cada linha é armazenada como uma lista de números N-bit que descrevem as cores dos quadrados. Agora podemos processar N colunas ao mesmo tempo usando operações de bits. No código a seguir, a cor[y][k]

representa um bloco de N cores como bits.

(40)

Contagem de Subgrid Contagem de Subgrid

O algoritmo resultante funciona em tempo O(n3 / N).

Geramos uma grade aleatória de tamanho 2500 × 2500 e comparamos a implementação original e otimizada por bits. Enquanto o código original demorou 29,6 segundos, a versão otimizada em bits só levou 3.1 segundos com N = 32 (números int) e 1,7 segundos com N = 64 (números long long).

int count = 0;

for (int i = 0; i <= n/N; i++) {

count += __builtin_popcount(color[a][i]&color[b][i]);

}

(41)

Programação dinâmica Programação dinâmica

As operações de bits fornecem uma maneira eficiente e conveniente de implementar algoritmos de programação dinâmicos cujos estados contêm subconjuntos de elementos, porque tais estados podem ser armazenados como números inteiros. Em seguida, discutiremos exemplos de combinações de operações de bits e programação dinâmica.

(42)

Seleção ótima Seleção ótima

Como primeiro exemplo, considere o seguinte problema: recebemos os preços dos produtos k durante n dias, e queremos comprar cada produto exatamente uma vez. No entanto, podemos comprar no máximo um produto por dia. Qual é o preço total mínimo? Por exemplo, considere o seguinte cenário (k = 3 e n = 8):

0 1 2 3 4 5 6 7

Produto 0 6 9 5 2 8 9 1 6

Produto 1 8 2 6 2 7 5 7 2

Produto 2 5 3 9 7 3 5 1 4

(43)

Seleção ótima Seleção ótima

Nesse cenário, o preço total mínimo é de 5:

0 1 2 3 4 5 6 7

Produto 0 6 9 5 2 8 9 1 6

Produto 1 8 2 6 2 7 5 7 2

Produto 2 5 3 9 7 3 5 1 4

(44)

Seleção ótima Seleção ótima

Deixe o preço[x][d] indicar o preço do produto x no dia d. Por exemplo, no preço de cenário acima [2][3]

= 7. Então, deixe o total (S, d) indicar o preço total mínimo para comprar um subconjunto S de produtos no dia d. Usando esta função, a solução para o problema é total ({0 ... k-1}, n-1).

Primeiro, total (;, d) = 0, porque não custa nada para comprar um conjunto vazio e total ({x}, 0) = preço[x]

[0], porque existe uma maneira de comprar um produto no primeiro dia. Então, a seguinte recorrência pode ser usada:

total(S , d)=min(total(S , d−1), min

x∈S

(total(

S x ,d−1)+ preço[x][d ]))

(45)

Seleção ótima Seleção ótima

Isso significa que nós não compramos nenhum produto no dia d ou compram um produto x que pertence a S. No último caso, removemos x de S e adicionamos o preço de x ao preço total.

O próximo passo é calcular os valores da função usando a programação dinâmica. Para armazenar os valores da função, declaramos uma matriz

int total[1<<K][N];

(46)

Seleção ótima Seleção ótima

onde K e N são constantes adequadamente grandes.

A primeira dimensão da matriz corresponde a uma representação de bits de um subconjunto.

Primeiro, os casos em que d = 0 podem ser processados da seguinte maneira:

for (int x = 0; x < k; x++) {

total[1<<x][0] = price[x][0];

}

(47)

Seleção ótima Seleção ótima

Então, a recorrência se traduz no seguinte código:

O tempo de complexidade do algoritmo é O(n2kk).

for (int d = 1; d < n; d++) {

for (int s = 0; s < (1<<k); s++) { total[s][d] = total[s][d-1];

for (int x = 0; x < k; x++) { if (s&(1<<x)) {

total[s][d] = min(total[s][d],

total[s^(1<<x)][d-1]+price[x][d]);

} }

} }

(48)

Permutações para Subconjuntos Permutações para Subconjuntos

Usando a programação dinâmica, muitas vezes é possível mudar uma iteração sobre as permutações para uma iteração sobre os subconjuntos. O benefício disso é que n!, número de permutações, é muito maior que 2n, o número de subconjuntos. Por exemplo, se n = 20, então n! ≈ 2.4 • 1018 e 2n ≈ 106. Assim, para certos valores de n, podemos passar eficientemente pelos subconjuntos, mas não através das permutações.

Como exemplo, considere o seguinte problema: há um elevador com peso máximo x e n pessoas com pesos conhecidos que desejam chegar do piso térreo até o último andar. Qual é o número mínimo de passeios necessários se as pessoas entram no elevador em uma ordem ótima?

(49)

Permutações para Subconjuntos Permutações para Subconjuntos

Por exemplo, suponha que x = 10, n = 5 e os pesos sejam os seguintes:

Neste caso, o número mínimo de passeios é 2. Uma ordem ótima é {0, 2, 3, 1, 4}, que divide as pessoas em dois passeios: primeiro {0, 2, 3} (peso total 10), e depois {1, 4} (peso total 9).

Pessoas Pesos 0

12 3 4

2 33 5 6

(50)

Permutações para Subconjuntos Permutações para Subconjuntos

O problema pode ser facilmente resolvido no tempo O(n!n) testando todas as permutações possíveis de n pessoas. No entanto, podemos usar a programação dinâmica para obter um algoritmo de tempo O(2nn) mais eficiente. A ideia é calcular para cada subconjunto de pessoas dois valores: o número mínimo de passeios necessários e o peso mínimo de pessoas que viajam no último grupo.

Deixe o peso [p] indicar o peso da pessoa p. Definimos duas funções: passeios (S) é o número mínimo de passeios para um subconjunto S, e o último (S) é o peso mínimo da última viagem. Por exemplo, no cenário acima

passeios({1, 3, 4}) = 2 e ultimo({1, 3, 4}) = 5,

(51)

Permutações para Subconjuntos Permutações para Subconjuntos

porque os passeios ótimos são {1, 4} e {3}, e o segundo passeio tem peso 5. Claro, nosso objetivo final é calcular o valor dos passeios ({0 ... n-1}).

Podemos calcular os valores das funções de forma recursiva e depois aplicar a programação dinâmica.

A ideia é passar por todas as pessoas que pertencem a S e a escolha otimamente é a última pessoa que entra no elevador. Cada uma dessas opções produz um subproblema para um subconjunto menor de pessoas. Se último(S \ p) + peso[p] ≤ x, podemos adicionar p para a última viagem. Caso contrário, temos que reservar uma nova viagem que inicialmente só contém p.

(52)

Permutações para Subconjuntos Permutações para Subconjuntos

Para implementar a programação dinâmica, declaramos uma matriz

que contém para cada subconjunto S um par (passeios (S), último (S)). Para um grupo vazio, não são necessários passeios:

pair<int,int> best[1<<N];

best[0] = {0,0};

(53)

Permutações para Subconjuntos Permutações para Subconjuntos

Então, podemos preencher a matriz da seguinte maneira:

for (int s = 1; s < (1<<n); s++) {

// valor inicial: são necessários n passeios best[s] = {n,0};

for (int p = 0; p < n; p++) { if (s&(1<<p)) {

auto option = best[s^(1<<p)];

if (option.second+weight[p] <= x) {

// adicionar p a um passeio existente option.second += weight[p];

} else {

// reserva uma nova viagem para p option.first++;

option.second = weight[p];

}

best[s] = min(best[s], option);

} }

}

(54)

Permutações para Subconjuntos Permutações para Subconjuntos

Observe que o loop acima garante que, para qualquer dois subconjuntos S1 e S2, que S1 ⊂ S2, processamos S1 antes de S2. Assim, os valores de programação dinâmica são calculados na ordem correta.

(55)

Contagem de subconjuntos Contagem de subconjuntos

Nosso último problema é o seguinte: Deixe X = {0. . . n - 1}, e cada subconjunto S X recebe um valor inteiro [S]. Nossa tarefa é calcular para cada S

ou seja, a soma dos valores dos subconjuntos de S.

Por exemplo, suponha que n = 3 e os valores são os seguintes:

soma(S)=

A⊂S

value[ A],

value[Ø] = 3

value[{0}] = 1

value[{1}] = 4

value[{2}] = 5

value[{0,1}] = 5

value[{0,2}] = 1

value[{1,2}] = 3

value[{0,1,2}] = 3

(56)

Contagem de subconjuntos Contagem de subconjuntos

Neste caso, por exemplo,

Como há um total de 2n subconjuntos, uma solução possível é passar por todos os pares de subconjuntos em tempo O(22n). No entanto, usando a programação dinâmica, podemos resolver o problema no tempo O(2nn). A ideia é focar em somas onde os elementos que podem ser removidos de S são restritos.

soma(0,2)=value[ ∅]+value[0]+value[2]+value[0,2]

soma(0,2)=3+1+5+1=10

(57)

Contagem de subconjuntos Contagem de subconjuntos

Deixe parcial (S, k) indicar a soma de valores de subconjuntos de S com a restrição que apenas elementos 0 … k pode ser removido de S. Por exemplo,

porque só podemos remover elementos 0 … 1.

Podemos calcular valores de soma usando valores de parcial, porque

parcial({0, 2}, 1) = value [{2}] + value [{0, 2}],

soma( S ) = parcial ( S, n − 1).

(58)

Contagem de subconjuntos Contagem de subconjuntos

Os casos de base para a função são

porque neste caso, nenhum elemento pode ser removido de S. Então, no caso geral, podemos usar a seguinte recorrência:

parcial(S , k)=

{

parcial(S , kparcial−1)+(S , kparcial−1)(S k ,k−1) kkSS

parcial ( S, − 1) = value [ S ],

(59)

Contagem de subconjuntos Contagem de subconjuntos

Aqui nos concentramos no elemento k. Se k S, temos duas opções: podemos manter k em S ou removê-lo de S.

Existe uma maneira particularmente inteligente de implementar o cálculo das somas. Podemos declarar um vetor

que conterá a soma de cada subconjunto. O vetor é inicializada da seguinte forma:

int soma[1<<N];

for (int s = 0; s < (1<<n); s++) { sum[s] = value[s];

}

(60)

Contagem de subconjuntos Contagem de subconjuntos

Então, podemos preencher a matriz da seguinte maneira:

Este código calcula os valores de parcial (S, k) para k

= 0 … n - 1 para a soma do vetor. Uma vez que parcial (S, k) é sempre baseada em parcial (S, k - 1), podemos reutilizar a soma do vetor, que produz uma implementação muito eficiente.

for (int k = 0; k < n; k++) {

for (int s = 0; s < (1<<n); s++) {

if (s&(1<<k)) sum[s] += sum[s^(1<<k)];

} }

(61)

Exercício Exercício

Você pode fazer eles no codepit.io

CIC 110 9 senha: unifei

Você pode fazer eles no codepit.io

CIC 110 9 senha: unifei

Referências

Documentos relacionados

dos rins em pacientes idosos, se houver caracterização de insuficiência renal grave, recomenda-se avaliar a relação risco/benefício antes de administrar Sintezys ® (vide item 4.

alimentares: de crianças menores de seis meses: Aleitamento Materno Exclusivo (AME): nenhum outro alimento é oferecido à criança, além do leite materno; Aleitamento Materno

 Caso tenha que alterar ou cancelar a reserva no dia da vacinação, ou não conseguir chegar ao local de vacinação no horário reservado, contate diretamente com a

A extensão (na maioria dos casos) não é requerida pelo sistema operacional GNU/Linux, mas é conveniente o seu uso para determinarmos facilmente o tipo de arquivo e que

Os Credores Operacionais que realizarem novos fornecimentos com Prazo Médio mínimo superior a 60 (sessenta) dias para pagamento, receberão 1% (um por cento) a mais, do

Table 3 Components of the TβD (Jaccard index) observed and comparison between the mean samples and standard deviation for primary ray-finned fishes in Brazil. JAC:

Uma vez formado o quadro, utilizá-lo ao seu potencial máximo significa atribuir tarefas de acordo com habilidades e competências de cada diretor, investir na

Dado os algoritmos a seguir, mostre que eles não en- contram a solução ótima do problema, ou seja, en- contre contraexemplos para cada um dos seguinte algoritmos para o problema..