Exclua alguns bits e conte

26

Considere todas as 2^ndiferentes cadeias binárias de comprimento ne assuma n > 2. Você tem permissão para excluir exatamente os b < n/2bits de cada uma das cadeias binárias, deixando as cadeias de comprimento n-brestantes. O número de strings distintas restantes depende de quais bits você exclui. Supondo que seu objetivo seja deixar o menor número possível de strings diferentes restantes, esse desafio é escrever um código para calcular o quão poucos você pode deixar em função de n.

Exemplo n=3e b = 1. Você pode deixar apenas as duas strings 11e 00.

Para n=9e b = 1,2,3,4temos70,18,6,2

Para n=8e b = 1,2,3temos40,10,4

Para n=7e b = 1,2,3temos20,6,2

Para n=6e b = 1,2temos12,4

Para n=5e b = 1,2temos6,2

Esta pergunta foi originalmente feita por mim em 2014 de uma forma diferente no MO .

Entrada e saída

Seu código deve incluir um número inteiro ne gerar um único número inteiro para cada valor de binício b = 0e aumento.

Ponto

Sua pontuação é a maior npara a qual seu código é concluído b < n/2em menos de um minuto no meu PC com Linux. Em caso de desempate, o maior que o bseu código atingir para as maiores nvitórias conjuntas . No caso de quebra de empate também nesse critério, o código mais rápido para os maiores valores de ne bdecide. Se o tempo estiver dentro de um ou dois segundos um do outro, a primeira resposta publicada vence.

Línguas e bibliotecas

Você pode usar qualquer idioma da biblioteca que desejar. Como eu tenho que executar o seu código, ajudaria se fosse gratuito (como na cerveja) e funcionasse no Linux.

Anush
fonte
Estou assumindo b > 0como requisito de entrada adicional? Ou seria n=3e b=0simplesmente produziria 2^ncomo resultado?
Kevin Cruijssen 28/05
@KevinCruijssen Deveria produzir 2^nmesmo.
Anush
Além disso, você diz que a entrada é única ne única b, mas a pontuação é a maior npela qual o código é concluído b < n/2em menos de um minuto. Não seria melhor ter uma única entrada nnesse caso e gerar todos os resultados 0 <= b < n/2? Ou devemos fornecer dois programas / funções: um recebendo duas entradas ne b, e outro recebendo apenas entradas ne saídas de todos os resultados no intervalo 0 <= b < n/2?
Kevin Cruijssen 28/05
2
Bem, eu já votei em seu desafio, então não posso fazê-lo novamente. :) Embora eu não tenha idéia de como calcular isso com eficiência (algoritmos O eficientes eram algo em que eu sempre fui ruim ... e uma das poucas disciplinas da faculdade de TI que tive que refazer algumas vezes), parece que um desafio muito interessante. Estou curioso para ver quais respostas as pessoas apresentam.
Kevin Cruijssen 28/05
2
Existe um exemplo de trabalho? Seria um bom lugar para começar, tanto em termos de correção quanto em comparação à velocidade.
Max28

Respostas:

6

Python 2.7 / Gurobi n = 9

Essa solução é um uso muito direto do solucionador ILP de Gurobi para os problemas de número inteiro misto (MIP) booleanos.

O único truque é eliminar a simetria no complemento de 1 para reduzir pela metade os tamanhos dos problemas.

Usando a licença "gratuita" por tempo limitado da Gurobi LLC, estamos restritos a 2000 restrições, mas a solução 10 del 1 está bem fora do prazo de 60 segundos no meu laptop.

from gurobipy import *
from itertools import combinations

def mincover(n,d):
    bs = pow(2,n-1-d)
    m = Model()
    m.Params.outputFlag = 0
    b = {}
    for i in range(bs):
      b[i] = m.addVar(vtype=GRB.BINARY, name="b%d" % i)
    m.update()
    for row in range(pow(2,n-1)):
      x = {}
      for i in combinations(range(n), n-d):
        v = 0
        for j in range(n-d):
          if row & pow(2,i[j]):
            v += pow(2,j)
        if v >= bs:
          v = 2*bs-1-v
        x[v] = 1
      m.addConstr(quicksum(b[i] for i in x.keys()) >= 1)
    m.setObjective(quicksum(b[i] for i in range(bs) ), GRB.MINIMIZE)
    m.optimize()
    return int(round(2*m.objVal,0))

for n in range(4,10):
    for d in range((n//2)+1):
        print n, d, mincover(n,d)

UPDATE + CORR: 10,2 possui o tamanho ideal da solução 31 (veja por exemplo) Gurobi não mostra nenhuma solução simétrica do tamanho 30 (retorna um problema inviável) .. [minha tentativa de mostrar viabilidade assimétrica em 30 permaneceu inconclusiva após 9,5 horas de execução], por exemplo, bit padrões de números inteiros 0 7 13 14 25 28 35 36 49 56 63 64 95 106 118 128 147 159 170 182 195 196 200 207 225 231 240 243 249 252 255ou0 7 13 14 19 25 28 35 36 49 56 63 64 95 106 118 128 159 170 182 195 196 200 207 225 231 240 243 249 252 255

Jayprich
fonte
Você quebrou o recorde de "recompensa infinita mais rápida"?
user202729
Não vejo nenhuma recompensa aqui, o que você quer dizer?
Jayprich
@ user202729 Sim. Defino muito baixo. Eu deveria tê-lo definido em n = 10 :)
Anush
Realmente resolvê-lo em n = 9 não é uma coisa fácil. É por isso que o OP usa uma biblioteca existente (que deveria ser melhor do que uma solução escrita à mão, como a minha).
user202729
1
Obrigado @ChristianSievers, vejo MO afirmar que 10,2 tem apenas ótimos assimétricos que não posso refutar nem verificar. Se eu remover o atalho de suposição de simetria que funciona até n = 9, Gurobi ainda pode resolver até n = 9 no tempo necessário.
Jayprich 13/06/19
3

C ++, n = 6

Força bruta com algumas pequenas otimizações.

#include<cassert>
#include<iostream>
#include<vector>

// ===========
/** Helper struct to print binary representation.
`std::cout<<bin(str,len)` prints (str:len) == the bitstring 
represented by last (len) bits of (str).
*/
struct bin{
    int str,len;
    bin(int str,int len):str(str),len(len){}
};
std::ostream& operator<<(std::ostream& str,bin a){
    if(a.len)
        return str<<bin(a.str>>1,a.len-1)<<char('0'+(a.str&1));
    else if(a.str)
        return str<<"...";
    else
        return str;
}
// ===========

/// A patten of (len) bits of ones.
int constexpr pat1(int len){
    return (1<<len)-1;
}

// TODO benchmark: make (res) global variable?

/**Append all distinct (subseqs+(sfx:sfxlen)) of (str:len) 
with length (sublen) to (res).
*/
void subseqs_(
    int str,int len,int sublen,
    int sfx,int sfxlen,
    std::vector<int>& res
){
    // std::cout<<"subseqs_ : str = "<<bin(str,len)<<", "
    // "sublen = "<<sublen<<", sfx = "<<bin(sfx,sfxlen)<<'\n';

    assert(len>=0);

    if(sublen==0){ // todo remove some branches can improve perf?
        res.push_back(sfx);
        return;
    }else if(sublen==len){
        res.push_back(str<<sfxlen|sfx);
        return;
    }else if(sublen>len){
        return;
    }

    if(str==0){
        res.push_back(sfx);
        return;
    }

    int nTrail0=0;
    for(int ncut;str&&nTrail0<sublen;

        ++nTrail0,
        ncut=__builtin_ctz(~str)+1, // cut away a bit'0' of str
        // plus some '1' bits
        str>>=ncut,
        len-=ncut
    ){
        ncut=__builtin_ctz(str)+1; // cut away a bit'1' of str
        subseqs_(str>>ncut,len-ncut,sublen-nTrail0-1,
            sfx|1<<(sfxlen+nTrail0),sfxlen+nTrail0+1,
            res
        ); // (sublen+sfxlen) is const. TODO global var?
    }

    if(nTrail0+len>=sublen) // this cannot happen if len<0
        res.push_back(sfx);
}

std::vector<int> subseqs(int str,int len,int sublen){
    assert(sublen<=len);
    std::vector<int> res;
    if(__builtin_popcount(str)*2>len){ // too many '1's, flip [todo benchmark]
        subseqs_(pat1(len)^str,len,sublen,0,0,res);
        int const p1sublen=pat1(sublen);
        for(int& r:res)r^=p1sublen;
    }else{
        subseqs_(str,len,sublen,0,0,res);
    }
    return res;
}

// ==========

/** Append all distinct (supersequences+(sfx:sfxlen)) of (str:len)
with length (suplen) to (res).
Define (a) to be a "supersequence" of (b) iff (b) is a subsequence of (a).
*/
void supseqs_(
    int str,int len,int suplen,
    int sfx,int sfxlen,
    std::vector<int>& res
){
    assert(suplen>=len);

    if(suplen==0){
        res.push_back(sfx);
        return;
    }else if(suplen==len){
        res.push_back(str<<sfxlen|sfx);
        return;
    }

    int nTrail0; // of (str)
    if(str==0){
        res.push_back(sfx);
        // it's possible that the supersequence is '0000..00'
        nTrail0=len;
    }else{
        // str != 0 -> str contains a '1' bit ->
        // supersequence cannot be '0000..00'
        nTrail0=__builtin_ctz(str);
    }
    // todo try `nTrail0=__builtin_ctz(str|1<<len)`, eliminates a branch
    // and conditional statement

    for(int nsupTrail0=0;nsupTrail0<nTrail0;++nsupTrail0){
        // (nsupTrail0+1) last bits of supersequence matches with 
        // nsupTrail0 last bits of str.
        supseqs_(str>>nsupTrail0,len-nsupTrail0,suplen-1-nsupTrail0,
            sfx|1<<(nsupTrail0+sfxlen),sfxlen+nsupTrail0+1,
            res);
    }

    int const strMatch=str?nTrail0+1:len; 
    // either '1000..00' or (in case str is '0000..00') the whole (str)

    for(int nsupTrail0=suplen+strMatch-len;nsupTrail0-->nTrail0;){
        // because (len-strMatch)<=(suplen-1-nsupTrail0),
        // (nsupTrail0<suplen+strMatch-len).

        // (nsupTrail0+1) last bits of supersequence matches with
        // (strMatch) last bits of str.
        supseqs_(str>>strMatch,len-strMatch,suplen-1-nsupTrail0,
            sfx|1<<(nsupTrail0+sfxlen),sfxlen+nsupTrail0+1,
            res);
    }

    // todo try pulling constants out of loops
}

// ==========

int n,b;
std::vector<char> done;
unsigned min_undone=0;

int result;
void backtrack(int nchoice){
    assert(!done[min_undone]);
    ++nchoice;
    std::vector<int> supers_s;
    for(int s:subseqs(min_undone,n,n-b)){
        // obviously (s) is not chosen. Try choosing (s)
        supers_s.clear();
        supseqs_(s,n-b,n,0,0,supers_s);
        for(unsigned i=0;i<supers_s.size();){
            int& x=supers_s[i];
            if(!done[x]){
                done[x]=true;
                ++i;
            }else{
                x=supers_s.back();
                supers_s.pop_back();
            }
        }

        unsigned old_min_undone=min_undone;
        while(true){
            if(min_undone==done.size()){
                // found !!!!
                result=std::min(result,nchoice);
                goto label1;
            }
            if(not done[min_undone])
                break;
            ++min_undone;
        }
        if(nchoice==result){
            // backtrack more will only give worse result
            goto label1;
        }

        // note that nchoice is already incremented
        backtrack(nchoice);

        label1: // undoes the effect of (above)
        for(int x:supers_s)
            done[x]=false;
        min_undone=old_min_undone;
    }
}

int main(){
    std::cin>>n>>b;

    done.resize(1<<n,0);
    result=1<<(n-b); // the actual result must be less than that

    backtrack(0);
    std::cout<<result<<'\n';
}

Executar localmente:

[user202729@archlinux golf]$ g++ -std=c++17 -O2 delbits.cpp -o delbits
[user202729@archlinux golf]$ time for i in $(seq 1 3); do ./delbits <<< "6 $i"; done
12
4
2

real    0m0.567s
user    0m0.562s
sys     0m0.003s
[user202729@archlinux golf]$ time ./delbits <<< '7 1'
^C

real    4m7.928s
user    4m7.388s
sys     0m0.173s
[user202729@archlinux golf]$ time for i in $(seq 2 3); do ./delbits <<< "7 $i"; done
6
2

real    0m0.040s
user    0m0.031s
sys     0m0.009s
user202729
fonte
1
Principalmente para incentivar outras pessoas a postar seu código, se for mais rápido que o meu.
User202729 de
Por favor? ... (nota: Esta é uma instância de um problema de cobertura de conjunto.) #
User202729 1/18/18
1
Eu estou trabalhando nisso. Eu simplesmente não consigo pensar em nenhuma maneira inteligente de fazer isso. Se mais ninguém postar uma resposta, eu colocarei a minha que só pode chegar a n = 4 até agora.
mypetlion