解决色彩难题


35

在我们Puzzling.SE的朋友那里,发布了以下拼图:这个半彩色拼图是否始终可以解决?由Edgar G.你可以发挥它在这里

拼图说明

给定一个m x n网格,其中包含三种不同颜色的图块,如果它们的颜色不同,则可以选择任意两个相邻图块。然后将这两个图块转换为第三种颜色,即这两个图块未表示的一种颜色。如果所有图块都具有相同的颜色,则难题就解决。显然,一个可以证明的是这个难题始终是可解的,如果没有m,也不n是被3整除。

8x8三色拼图

当然,这乞求一种求解算法。您将编写解决此难题的函数或程序。请注意,stdout明确允许具有“副作用”的函数(即,输出处于打开状态,而不是某些尴尬的数据类型返回值)。

输入输出

输入将是一个m x n由整数的矩阵123(或012如果方便的话)。您可以以任何理智的格式接受此输入。这两个mn>1,而不是被3整除你可以假设这一难题没有解决

然后,您将解决难题。这将涉及重复选择两个要“转换”的相邻图块(请参见上文)。您将为求解算法采取的每个步骤输出这些图块的两个坐标。这也可以是任何合理的输出格式。您可以自由选择从0索引到1索引之间的索引,还是先索引行还是列。但是,请在您的答案中提及这一点。

您的算法应在合理的时间内在原始8x8情况下运行。暴力破解它完全是明令禁止的,即你的算法应该下运行O(k^[m*(n-1)+(m-1)*n])k所需的解决方案的步数。但是,该解决方案并不需要是最优的。链接问题中给出的证明可以使您了解如何执行此操作(例如,首先仅使用垂直相邻的图块来创建所有列,然后再执行所有行)

测试用例

在这些测试用例中,坐标是基于1的,并且首先对行进行了索引(如MATLAB / Octave以及可能的许多其他索引)。

Input: 
[1 2]
Output: (result: all 3's)
[1 1],[1,2]


Input:
[ 1 2
  3 1 ]
Output: (result: all 1's)
[1 1],[2 1]        (turn left column into 2's)
[2 1],[2 2]        (turn right column into 3's)
[1 1],[1 2]        (turn top row into 1's)
[2 1],[2 2]        (turn bottom row into 1's)

Input:
[1 2 3 2
 3 2 1 1]

Output: (result: all 3's)
[1 1],[1 2] 
[1 3],[1 4] 
[1 2],[1 3] 
[1 1],[1 2] 
[1 2],[1 3] 
[1 1],[1 2]
[1 3],[1 4]
[2 1],[2 2]
[1 1],[2 1]
[1 2],[2 2]
[1 3],[2 3]
[1 4],[2 4]

如果需要,我可以发布较大测试用例的pastebin,但是我认为这应该足够了。


我很乐意看到此代码的挑战版本,其目标是用最少的总动作来解决一系列难题。
Mego

@Mego我绝对考虑过。但是,恐怕它将变成永久运行的DFS或BFS 。或者,为避免这种情况,设置了一些模糊的限制(例如“必须在一个小时内运行”,这会有利于拥有大型计算机的人,或者要求我测试所有解决方案)。而且,当前的挑战有零答案是我的,所以我怀疑一个更困难的版本(需要启发式算法等)是否会更受欢迎……但是,如果这个挑战获得动力,我可能会像你一样发布一个同级的挑战描述。
桑契斯'16

我想我会在Lua中尝试这样做,但它可能比您的324字节解决方案要长^^
Katenkyo

@Katenkyo只有一种找出答案的方法!我期待着您的解决方案。
桑契斯'16

您将不得不悲伤地等待一点,您避免了蛮力解决方案,所以我必须找到lua不够的解决方案:p
Katenkyo

Answers:


5

Ruby,266个字节

大约只是Octave解决方案的一部分,除了它首先解决的是行而不是列。输入是一个数组数组,内部数组是行。输出动作为[row, column, row, column]测试套件

->m{t=->v{v.size*v.inject(:+)%3}
s=->a,x,r{g=t[a]
(q=(r=0..a.size-2).find{|i|a[i]!=a[i+1]&&g!=a[i]}||r.find{|i|a[i]!=a[i+1]}
a[q,2]=[t[a[q,2]]]*2
p r ?[x,q,x,q+1]:[q,x,q+1,x])while[]!=a-[g]}
m.size.times{|i|s[m[i],i,1]}
m=m.shift.zip *m
m.size.times{|i|s[m[i],i,p]}}

毫无解释

->m{                                  # Start lambda function, argument `m`
  t=->v{v.size*v.inject(:+)%3}        # Target color function
  s=->a,x,r{                          # Function to determine proper moves
                                      #   a = row array, x = row index, r = horizontal
    g=t[a]                            # Obtain target color
    (
      q=(r=0..a.size-2).find{|i|      # Find the first index `i` from 0 to a.size-2 where...
        a[i]!=a[i+1]                  # ...that element at `i` is different from the next...
        &&g!=a[i]                     # ...and it is not the same as the target color
      } || r.find{|i|a[i]!=a[i+1]}    # If none found just find for different colors
      a[q,2]=[t[a[q,2]]]*2            # Do the color flipping operation
      p r ?[x,q,x,q+1]:[q,x,q+1,x]    # Print the next move based on if `r` is truthy
    ) while[]!=a-[g]}                 # While row is not all the same target color, repeat
m.size.times{|i|                      # For each index `i` within the matrix's rows...
  s[m[i],i,1]                         # ...run the solving function on that row
                                      #   (`r` is truthy so the moves printed are for rows)
}
m=m.shift.zip *m                      # Dark magic to transpose the matrix
m.size.times{|i|s[m[i],i,p]}}         # Run the solving function on all the columns now
                                      #   (`r` is falsy so the moves printed are for columns)

有趣的是,两种非高尔夫语言之间的端口仍然可以产生约20%的差异。您能否加上一个简短的解释?(特别是第3行-我暗中希望我可以在答案中使用它,因为intersect关键字

@sanchises添加了一个说明。关于intersect,我不知道您是否可以解决我的工作方式,因为Ruby find基本上是在函数上运行的,而且您的function关键字也很庞大。
价值墨水

我实际上仍然可以将您的方法用于find-谢谢!但是,
尚无处

13

八度,334313字节

由于挑战似乎有些艰巨,所以我提出自己的解决方案。我没有正式证明这种方法有效(我想这可以证明该算法永远不会陷入循环中),但是到目前为止,它仍然可以完美地工作,可以在15秒内完成100x100个测试用例。请注意,我选择使用具有副作用的函数,而不是使用返回所有坐标的函数,因为这节省了我几个字节。坐标以行为主,从1开始,格式为row1 col1 row2 col2。输入颜色是0,1,2因为这样可以更好地工作mod,但不得不使用numel而不是使用nnz。高尔夫版本:编辑:通过使用凯文·刘的答案中的一种技术,节省了另外几个字节。

function P(p)
k=0;[m,n]=size(p);t=@(v)mod(sum(v)*numel(v),3);for c=1:n
p(:,c)=V(p(:,c));end
k=1;for r=1:m
p(r,:)=V(p(r,:));end
function a=V(a)
while any(a-(g=t(a)))
isempty(q=find(diff(a)&a(1:end-1)-g,1))&&(q=find(diff(a),1));
a([q q+1])=t(a([q q+1]));if k
disp([r q r q+1])
else
disp([q c q+1 c])
end;end;end;end

求解算法的示例GIF:

在此处输入图片说明

非高尔夫版本:

function solveChromaticPuzzle(p)
[m,n]=size(p);                           % Get size
t=@(v)mod(sum(v)*numel(v),3);            % Target colour function
for c=1:n                                % Loop over columns
    p(:,c)=solveVec(p(:,c));             % Solve vector
end
for r=1:m                                % Loop over rows
    p(r,:)=solveVec(p(r,:));
end
    function a=solveVec(a)               % Nested function to get globals
        g=t(a);                          % Determine target colour
        while(any(a~=g))                 % While any is diff from target...
            % Do the finding magic. Working left-to-right, we find the
            % first pair that can be flipped (nonzero diff) that does not
            % have the left colour different from our goal colour
            q=min(intersect(find(diff(a)),find(a~=g)));
            if(isempty(q))               % In case we get stuck...
                q=find(diff(a),1);       % ... just flip the first possible
            end;
            a([q q+1])=t(a([q q+1]));    % Do the actual flipping.
            if(exist('r'))               % If we're working per row
                disp([r q r q+1])        % Print the pair, using global row
            else
                disp([q c q+1 c])        % Print the pari, using global col
            end
        end
    end
end

刚刚注意到,但我的名字不是Kenny Lau ...这是另一个用户,我的用户名明确指出我不是Kenny
Value Ink

7

Lua中,594个 575个 559字节

警告在完成高尔夫球之前,还有很多工作要做!我至少应该能够做到500字节以下。目前,这是第一个可行的解决方案,而我仍在努力之中。

完成后,我将提供完整的解释。

function f(t)s=#t a=","for i=1,s do p=t[i]for i=1,s
do p.Q=p.Q and p.Q+p[i]or p[i]end p.Q=(p.Q*#p)%3 for i=1,s do for j=1,#p-1 do
x=p[j]y=p[j+1]u=x~=y and(p.S and p.R==p.S or x~=p.Q)v=(x+y)*2p[j]=u and v%3or x
p[j+1]=u and v%3or y print(i..a..j,i..a..j+1)end
p.R=p.S p.S=table.concat(p)end end
for i=1,s do Q=Q and Q+t[i][1]or t[i][1]end Q=(Q*s)%3 for i=1,s
do for j=1,s-1 do p=t[j]q=t[j+1]x=p[1]y=q[1]u=x~=y and(S and R==S or x~=Q)v=(x+y)*2
for k=1,#p do p[k]=u and v%3or x q[k]=u and v%3or y
print(j..a..k,j+1..a..k)end Y=Y and Y..x or x end
R=S S=Y end end

5

Rust,496495字节

遗憾的是我无法击败OP,但是对于Rust的回答,我对字节数非常满意。

let s=|mut v:Vec<_>,c|{
let p=|v:&mut[_],f,t|{
let x=|v:&mut[_],i|{
let n=v[i]^v[i+1];v[i]=n;v[i+1]=n;
for k in f..t+1{print!("{:?}",if f==t{(k,i,k,i+1)}else{(i,k,i+1,k)});}};
let l=v.len();let t=(1..4).find(|x|l*x)%3==v.iter().fold(0,|a,b|a+b)%3).unwrap();
let mut i=0;while i<l{let c=v[i];if c==t{i+=1;}else if c==v[i+1]{
let j=if let Some(x)=(i+1..l).find(|j|v[j+1]!=c){x}else{i-=1;i};x(v,j);}else{x(v,i);}}t};
p(&mut (0..).zip(v.chunks_mut(c)).map(|(i,x)|{p(x,i,i)}).collect::<Vec<_>>(),0,c-1usize)};

输入:数字和列数的向量。例如

s(vec!{1,2,1,3},2);

输出

 (row1,col1,row2,col2)

到命令行。

我首先求解每一行,然后仅求解一次结果列,但是打印所有列的步骤。因此,它实际上是相当有效的:-P。

使用格式:

let s=|mut v:Vec<_>,c|{  
    let p=|v:&mut[_],f,t|{     // solves a single row/column
        let x=|v:&mut[_],i|{   // makes a move and prints it 
            let n=v[i]^v[i+1]; // use xor to calculate the new color
            v[i]=n;
            v[i+1]=n;
            for k in f..t{
                print!("{:?}",if f==t{(k,i,k,i+1)}else{(i,k,i+1,k)});
            }
        };
        let l=v.len();
        // find target color
        // oh man i am so looking forward to sum() being stabilized
        let t=(1..4).find(|x|(l*x)%3==v.iter().fold(0,|a,b|a+b)%3).unwrap();
        let mut i=0;
        while i<l{
            let c=v[i];
            if c==t{             // if the color is target color move on
                i+=1;
            }else if c==v[i+1]{ // if the next color is the same
                                // find the next possible move
                let j=if let Some(x)=(i+1..l).find(|j|v[j+1]!=c){x}else{i-=1;i};
                x(v,j);
            }else{              // colors are different so we can make a move
                x(v,i);         
            }
        }
        t
    };
    // first solve all rows and than sovle the resulting column c times 
    p(&mut (0..).zip(v.chunks_mut(c)).map(|(i,x)|p(x,i,i)).collect::<Vec<_>>(),0,c-1usize)
};

编辑: 现在返回解决方案的颜色,为我节省了分号^^


5

Befunge197 368 696 754个字节


(是的,我正在做一些反向编码,字节越多越好)


我当时认为用Befunge编写该算法可能是一个挑战,而且可能很有趣

我希望它是一个社区计划,因此,如果有人想从事该计划,请这样做。

最后,到目前为止,我还是一个人完成了所有工作,所以我会自己完成(差不多完成了)


尚未完成:巨魔型代码

&:19p&:09p:v:p94g92g90  <
 v94+1:g94&_$59g1+59p1-:|
 >p59gp1-: ^    vp95g93$<
v94\-1<v:g<     >  v
>g:1+v^_$v^90p94g92<
v5p94<   3>1+59p   ^
>9gg+\:^ %g   v93:g95<           v3_2         v
v1pg95g94<^95<>g-v>$v^           v ^-%3*2\gg9<
>9g39g+59g-1-|v-1_^ #^pg95+g92g90<1_09g:29g+5^
       ;  >  >  09g:29g+59gg\3%-# !^v         <
          ^p95<                  ^  <
     v  p96-1+g90g92<
     v                  p95_@
            >59g1+:39g-19g-^
     v    >p 79g:59gg\1+59gp$$$$$29g49pv
    > p59g^ |<<<<<<<<<<<<<<<<<<!-g96g94<
>:79^>29g49p>69g1+59gg49g:59gg\1+49p- v
^\-\6+gg95+1\g< v         !-g96:<-1g94_^
>"]",52*,::59g^v_::1+59gg\59gg-v^ <
^ .-g93g95,.-g<>:69g- v  v-g96:_1+^
>+" [,]",,,\29^       >#v_$:49g2-v
^1:.-g93g95,.-g92\,"[ ":<        <

(是的,这是一个巨魔,相信我)


基本上,它会读取一个数组并计算移动以解决行,给定输入为

(number of rows) (number of columns) 1 2 3 1 1 3 2 1 2 ....

(整个数组作为列表传递[行1,行2,行3,…])

输出是

[col row],[col',row']
[col2 row2],[col2',row2']
...

行和列都从0开始。


现在,行已解决,几乎完成了!万岁!


说明:(稍后会更新)

图片

因此,有5个主要部分:

  • 第一个为绿色,读取输入行并写入阵列的一行
  • 第二个,橙色,传递到数组的下一行
  • 蓝色的第三个求和行
  • 第四个是粉红色,取总和的模数3,将其保存在相关行的右侧,然后转到下一行
  • 最后,红色为根据先前计算的数字计算目标颜色的部分。这部分确实很笨,应该重写,但是我没有弄清楚如何才能很好地做到这一点(仅从那部分的197字节传递到368字节)

灰色部分是初始化


这是对要组合的to框的模块的更深入说明(顺便说一下,在此处进行了编码)

                                       B
            @                          v
            |                  !-g96g94<
ENTRY>29g49p>69g1+59gg49g:59gg\1+49p- v
                v         !-g96:<-1g94_^
               v_::1+59gg\59gg-v^ <
               >:69g- v  v-g96:_1+^
                      >#v_$:49g2-v
                    CALL<        <

CALL部分是当指令指针转到另一个模块时,将其组合为框。通过“ B”条目返回此模块

这是一些伪代码:(currentx与数组读取有关)对于:

    69g1+59gg  // read target color
    49g:59gg\1+49p // read current color and THEN shift the currentx to the next box
    if ( top != top ){  // if the current color is bad
        49g1-          //  put the current place  (or currentx - 1) in the stack
        While:
            if ( :top != 69g ){   // if you didn't reach the end of the list
                ::1+              // copy twice, add 1
                if ( 59gg == \59gg ){ // if the next color is the same than current
                   1+                // iterate
                   break While;
                }
            }

        : // copies j (the previous raw iterator)
        if ( top == 69g ){  // if you reached the end of the row (which mean you can't swap with the color after that point)
            $:              // discard j's copy and copy target
            49g2-           // put the place just before the color change on the stack
            combine_with_next;
        } else {
            combine_with_next;
        }
        29g49p   // back to the beginning of the row (there was some changes int the row)
    }

    if ( 49g != 69g ) // if you didn't reach the end of the list
        break For:

请注意,如果要测试它,则必须使用一些尾随空格和尾随新行,以便有足够的空间来存储数组(如果要使用我链接的解释)。22 +输入中的行数作为尾行,而34 +列数的行尾作为一行应该没问题。


只是好奇,为什么这种不竞争?
Value Ink

因为这一部分:“我希望它成为一个社区计划”。我以为否则我会有点作弊
Maliafo '16

结果是197个字节,您在Windows下工作吗?(而\r\n不是\n只算在内?)
Katenkyo,2016年

嗯,我想我在计数字节时要复制粘贴一些尾

如果最后我是唯一一位做到这一点的人,我会
省略

2

C,404字节

我第一次打高尔夫球,对结果感到非常满意。不过,花了太长时间。它不是完全标准的C语言,只是可以在gcc下编译而没有特殊标志(并且忽略警告)的任何东西。因此,其中有一个嵌套函数。该函数f将维度mn作为其第一个参数,并将其作为第三个参数,将一个(整数指针)指向一个大小为m× n(首先由行索引)的数组。其他参数是伪参数,您不需要传递它们,它们只是在那里保存字节以声明变量。它将每个更改的对以格式写入STDOUT中row1,col1:row1,col1;,用分号分隔对。使用基于0的索引。

#define A a[i*o+p
#define B A*c
f(m,n,a,i,j,o,p,b,c,d)int*a;{
int t(x){printf("%d,%d:%d,%d;",b?i:c+x,b?c+x:i,b?i:c+1+x,b?c+1+x:i);}
o=n;p=b=1;for(;~b;b--){
for(i=0;i<m;i++){c=j=0;
for(;j<n;)c+=A*j++];d=c*n%3;
for(j=0;j<n-1;j++) 
while(A*j]^d|A*j+p]^d){
for(c=j;B]==B+p];c++);
if(c<n-2&B]==d&2*(B+p]+A*(c+2)])%3==d)
B+p]=A*(c+2)]=d,t(1);else
B]=B+p]=2*(B]+B+p])%3,
t(0);}}o=m;p=m=n;n=o;o=1;}}

我使用与OP稍有不同的算法来求解单个行/列。它是这样的(伪代码):

for j in range [0, rowlength):
    while row[j] != targetCol or row[j+1] != targetCol:
        e=j
        while row[e] == row[e+1]:
            e++             //e will never go out of bounds
        if e<=rowLength-3 and row[e] == targetCol 
                and (row[e+1] != row[e+2] != targetCol):
            row.changeColor(e+1, e+2)
        else:
            row.changeColor(e, e+1)

for(;~b;b--)循环正好执行两次,第二遍则解决列而不是行。这是通过交换nm,并更改在指针算术中用于寻址数组的o和的值p来完成的。

这是一个没有测试版本的版本,带有测试主目录,并且在每次移动后打印整个数组(按Enter键进入第1步):

#define s(x,y)b?x:y,b?y:x
#define A a[i*o+p
#define B A*e
f(m,n,a,i,j,o,p,b,c,d,e)int*a;{

    int t(x){
        printf("%d,%d:%d,%d;\n",s(i,e+x),s(i,e+1+x));
        getchar();
        printf("\n");
        for(int i2=0;i2<(b?m:n);i2++){
            for(int j2=0;j2<(b?n:m);j2++){
                printf("%d ",a[i2*(b?n:m)+j2]);
            }
            printf("\n");
        }
        printf("\n");
    }

    printf("\n");
    b=1;
    for(int i2=0;i2<(b?m:n);i2++){
        for(int j2=0;j2<(b?n:m);j2++){
            printf("%d ",a[i2*(b?n:m)+j2]);
        }
        printf("\n");
    }
    printf("\n");

    o=n;p=1;
    for(b=1;~b;b--){
        for(i=0;i<m;i++){
            c=0;
            for(j=0;j<n;j++) c+= a[i*o+p*j];
            d=0;
            d = (c*n)%3;
            for(j=0;j<n-1;j++) {
                while(a[i*o+p*j]!=d||a[i*o+p*j+p]!=d){
                    for(e=j;a[i*o+p*e]==a[i*o+p*e+p];e++);
                    if(e<=n-3 && a[i*o+p*e]==d 
                            && 2*(a[i*o+p*e+p]+a[i*o+p*(e+2)])%3==d){
                        a[i*o+p*e+p]=a[i*o+p*(e+2)]=d;
                        t(1);
                    }else{
                        a[i*o+p*e]=a[i*o+p*e+p] = 2*(a[i*o+p*e]+a[i*o+p*e+p])%3;
                        t(0);
                    }
                }
            }
        }
        o=m;p=m=n;n=o;o=1;
    }
}

main(){
    int g[11][11] = 
    {
        {0,2,1,2,2,1,0,1,1,0,2},
        {2,1,1,0,1,1,2,0,2,1,0},
        {1,0,2,1,0,1,0,2,1,2,0},
        {0,0,2,1,2,0,1,2,0,0,1},
        {0,2,1,2,2,1,0,0,0,2,1},
        {2,1,1,0,1,1,2,1,0,0,2},
        {1,0,2,1,0,1,0,2,2,1,2},
        {0,0,2,1,2,0,1,0,1,2,0},
        {1,2,0,1,2,0,0,2,1,2,0},
        {2,1,1,0,1,1,2,1,0,0,2},
        {0,2,1,0,1,0,2,1,0,0,2},
    };
    #define M 4
    #define N 7
    int grid[M][N];
    for(int i=0;i<M;i++) {
        for(int j=0;j<N;j++) {
            grid[i][j] = g[i][j];
        }
    }
    f(M,N,grid[0],0,0,0,0,0,0,0,0);
};

只是出于好奇:您为什么选择其他算法(就字节节省而言)?
桑契斯'16

1
我认为当人们提出不同的解决方案时会更有趣,并且从一些快速测试中,我估计这两种方法的字节数大致相同。我可能还会尝试您的算法,看看是否可以降低。
Norg74 '16

在这里发布此内容是因为我没有足够的代表对此问题发表评论。对每一行然后分别对每一列进行暴力破解是否有效?从技术上讲,这不是“完全强制实施”,并且应低于指定的时间复杂度界限。我实际上考虑过这样做。
Norg74 '16

蛮横的提法是为了强化“合理的时间”的说法,因此将其视为t«O(...)。我知道在蛮力和合理的算法之间存在一个灰色区域,因此请使用您自己的判断来确定它是否正在解决难题,或者是否只是对DFS或BFS的微小修改而与“进度”无关。
桑契斯'16
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.