Integer Sequences (SEQUENCE)

問題概要

整数N,a,b(N<15, a,b < 2^N)が与えられる。0~2^N-1を隣接する要素がハミング距離1になるように並び替えた解き、a,bが隣接(環状に考えて)しないようなものがあればそれを出力する問題。

解法

こういう数列にはグレイコードという名前が付いているらしい。調べるとa_i = i ^ (i>>1)で生成できるらしい。ちょっと考えるとN=1,2のときは解がないとわかる(もちろんa,bのハミング距離が1のとき)。それ以上のときは、「まあ適当に置換してやれば大丈夫だろう」と考える。つまり、2進表記で縦に並べたとき、i列目とj列目を入れ換えてやる。ただしこの方法だと、唯一N=3, a=2, b=3のときに解を出力しないので、その場合は自前で埋め込む。以下は向こうでテストできないので適当にテストケースを作って試すプログラム。

感想

こんな考え方じゃコンテスト中には解けないでしょう。

#include <cstdio>
#include <cstdlib>
using namespace std;

int tmp[1<<16];
int ans[1<<16];
int N, a, b;

void print(){
    if(0)for(int i=0; i<1<<N; i++)
        printf("%d%c",ans[i],i==(1<<N)-1?'\n':' ');
}

bool check(){
    for(int i=0; i<1<<N; i++)
        if(ans[i]==a && (ans[(i+1)%(1<<N)]==b || ans[(i+(1<<N)-1)%(1<<N)]==b))
            return false;
    print();
    return true;
}

void solve(){
    if(a>b){
        int t=a;
        a = b;
        b = t;
    }
    if(N==3 && a==2 && b==3){
        //puts("1 3 7 6 2 0 4 5");
        return ;
    }
    if(N<=2 && __builtin_popcount(a^b)==1){
        puts("-1");
        return ;
    }
    for(int i=0; i<1<<N; i++){
        ans[i] = tmp[i] = i ^ (i>>1);
    }
    if(check()) return ;
    for(int m=0; m<N; m++)if((1<<m) != (a^b)){
        int mask = (1<<m) | (a^b);
        for(int i=0; i<1<<N; i++)
            ans[i] = (tmp[i]&(~mask)) | 
                ( (((tmp[i]>>m)&1)?(a^b):0) | ((tmp[i]&(a^b))?1<<m:0) );
        if(check()) return ;
    }
    printf("%d %d %d ", N, a, b);
    puts("-1");
    return ;
}

int main(){
    int T;
    N = 15; a = 3; b = 2;
    solve();
    srandom(397511423);
    for(int t=0; t<1000000; t++){
        N = random()%12 + 3;
        a = random()%(1<<N);
        b = a ^ (1<<(random()%N));
        solve();
    }
    /*
    for(scanf("%d",&T);T--;){
        scanf("%d%d%d",&N,&a,&b);
        solve();
    }
    */
    return 0;
}