Eliminar algunos bits y contar


26

Considere todas las 2^ndiferentes cadenas binarias de longitud ny asuma n > 2. Se le permite eliminar exactamente b < n/2bits de cada una de las cadenas binarias, dejando cadenas de longitud n-brestante. El número de cadenas distintas restantes depende de los bits que elimine. Asumiendo que su objetivo es dejar el menor número posible de cadenas diferentes, este desafío es escribir código para calcular qué tan pocos puede dejar en función de n.

Ejemplo n=3y b = 1. Puede dejar solo las dos cadenas 11y 00.

Para n=9y b = 1,2,3,4tenemos70,18,6,2

Para n=8y b = 1,2,3tenemos40,10,4

Para n=7y b = 1,2,3tenemos20,6,2

Para n=6y b = 1,2tenemos12,4

Para n=5y b = 1,2tenemos6,2

Esta pregunta fue originalmente planteada por mí en 2014 en una forma diferente en MO .

Entrada y salida

Su código debe incluir un entero ny generar un solo entero para cada valor de bcomenzar b = 0y aumentar.

Puntuación

Su puntaje es el más grande npara el cual su código se completa para todos b < n/2en menos de un minuto en mi PC con Linux. En caso de desempate, cuanto mayor sea bsu código para las mayores nganancias conjuntas . En caso de desempate también en ese criterio, el código más rápido para los valores más grandes de ny bdecide. Si los tiempos están dentro de un segundo o dos entre sí, la primera respuesta publicada gana.

Idiomas y bibliotecas

Puede usar cualquier idioma de la biblioteca que desee. Debido a que tengo que ejecutar su código, ayudaría si fuera gratis (como en cerveza) y funcionara en Linux.


¿Asumo b > 0como requisito de entrada adicional? ¿O simplemente n=3y como resultado? b=02^n
Kevin Cruijssen

@KevinCruijssen Debería salir de 2^nhecho.
Anush

Además, usted dice que la entrada es única ny única b, pero la puntuación es la más grande npara la cual el código completa todo b < n/2en menos de un minuto. ¿No sería mejor tener una sola entrada nen ese caso y generar todos los resultados 0 <= b < n/2? O debemos proporcionar dos programas / funciones: uno teniendo dos entradas ny b, y uno tomando solamente la entrada ny la salida de todos los resultados en el rango 0 <= b < n/2?
Kevin Cruijssen

2
Bueno, ya había votado tu desafío, así que no puedo volver a hacerlo. :) Aunque no tengo idea de cómo calcular esto de manera eficiente (los algoritmos O eficientes fueron algo en lo que siempre he sido malo ... y una de las pocas materias en la universidad de TI que tuve que rehacer un par de veces), parece que Un reto muy interesante. Tengo curiosidad por ver qué respuestas se les ocurren a las personas.
Kevin Cruijssen

2
¿Hay un ejemplo de trabajo? Sería un buen lugar para comenzar, tanto en términos de corrección como de comparación de velocidad.
maxb

Respuestas:


6

Python 2.7 / Gurobi n = 9

Esta solución es un uso muy directo del solucionador ILP de Gurobi para los problemas booleanos de enteros mixtos (MIP).

El único truco es eliminar la simetría en el complemento de 1 para reducir a la mitad los tamaños del problema.

Al usar la licencia "gratuita" de tiempo limitado de Gurobi LLC, estamos restringidos a 2000 restricciones, pero resolver 10 del 1 está muy por encima del límite de tiempo de 60 segundos de todos modos en mi computadora portátil.

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)

ACTUALIZACIÓN + CORR: 10,2 tiene un tamaño de solución óptimo 31 (ver, por ejemplo) Gurobi muestra que no existe una solución simétrica de tamaño 30 (el problema no es factible). patrones de enteros 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 255o0 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


¿Rompiste el récord de "recompensa infinita más rápida reclamada"?
usuario202729

No veo ninguna recompensa aquí, ¿qué quieres decir?
jayprich

@ user202729 Sí ... lo configuré demasiado bajo. Debería haberlo establecido en n = 10 :)
Anush

En realidad, resolverlo en n = 9 no es una cosa fácil. Es por eso que OP usa una biblioteca existente (que se supone que es mejor que una solución escrita a mano, como la mía).
user202729

1
Gracias @ChristianSievers Veo que MO afirma que 10,2 solo tiene óptimos asimétricos que no puedo refutar ni verificar. Si elimino el método abreviado de suposición de simetría que funciona hasta n = 9, resulta que Gurobi aún puede resolver hasta n = 9 en el tiempo requerido.
jayprich

3

C ++, n = 6

Fuerza bruta con algunas pequeñas optimizaciones.

#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';
}

Ejecutar 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

1
Principalmente para alentar a otros a publicar su código si es más rápido que el mío.
user202729

¿Por favor? ... (nota: esta es una instancia de un problema de cobertura de conjunto.)
user202729

1
Estoy trabajando en ello. Simplemente no puedo encontrar ninguna forma inteligente de hacerlo. Si nadie más publica una respuesta, pondré la mía que solo puede llegar hasta n = 4 hasta ahora.
mypetlion
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.