เรียงแอปเปิ้ล!


11

ปัญหา

ลองนึกภาพ 7 ถังเรียงกันเป็นแถว ที่เก็บข้อมูลแต่ละอันสามารถมีแอปเปิ้ลได้สูงสุด 2 ตัว มี 13 แอปเปิ้ลที่มีป้ายกำกับ 1 ถึง 13 พวกเขามีการกระจายในหมู่ 7 ถัง ตัวอย่างเช่น,

{5,4}, {8,10}, {2,9}, {13,3}, {11,7}, {6,0}, {12,1}

โดยที่ 0 หมายถึงพื้นที่ว่าง ลำดับที่แอปเปิ้ลปรากฏใน แต่ละที่ฝากข้อมูลไม่เกี่ยวข้อง (เช่น {5,4} เทียบเท่ากับ {4,5})

คุณสามารถย้ายแอปเปิ้ลใดก็ได้จากที่ฝากข้อมูลหนึ่งไปยังที่ฝากข้อมูลที่อยู่ติดกันโดยมีที่ว่างในที่ฝากข้อมูลปลายทางสำหรับแอปเปิ้ลอื่น การย้ายแต่ละครั้งจะอธิบายตามจำนวนแอปเปิลที่คุณต้องการย้าย (ซึ่งไม่ชัดเจนเนื่องจากมีพื้นที่ว่างเพียงอันเดียว) ตัวอย่างเช่นการใช้การย้าย

7

การจัดเรียงข้างต้นจะส่งผลให้

{5,4}, {8,10}, {2,9}, {13,3}, {11,0}, {6,7}, {12,1}

วัตถุประสงค์

เขียนโปรแกรมที่อ่านข้อตกลงจาก STDIN และเรียงลำดับลงในข้อตกลงต่อไปนี้

{1,2}, {3,4}, {5,6}, {7,8}, {9,10}, {11,12}, {13,0}

ใช้การเคลื่อนไหวน้อยที่สุด อีกครั้งลำดับที่แอปเปิ้ลปรากฏ ในที่เก็บข้อมูลแต่ละชุดไม่เกี่ยวข้องกัน คำสั่งของถังมีความสำคัญ มันควรจะออกการเคลื่อนไหวที่ใช้ในการเรียงลำดับการจัดเรียงแต่ละคั่นด้วยเครื่องหมายจุลภาค ตัวอย่างเช่น,

13, 7, 6, ...

คะแนนของคุณเท่ากับผลรวมของจำนวนการเคลื่อนไหวที่ต้องใช้ในการแก้ไขข้อตกลงต่อไปนี้:

{8, 2}, {11, 13}, {3, 12}, {6, 10}, {4, 0}, {1, 7}, {9, 5}
{3, 1}, {6, 9}, {7, 8}, {2, 11}, {10, 5}, {13, 4}, {12, 0}
{0, 2}, {4, 13}, {1, 10}, {11, 6}, {7, 12}, {8, 5}, {9, 3}
{6, 9}, {2, 10}, {7, 4}, {1, 8}, {12, 0}, {5, 11}, {3, 13}
{4, 5}, {10, 3}, {6, 9}, {8, 13}, {0, 2}, {1, 7}, {12, 11}
{4, 2}, {10, 5}, {0, 7}, {9, 8}, {3, 13}, {1, 11}, {6, 12}
{9, 3}, {5, 4}, {0, 6}, {1, 7}, {12, 11}, {10, 2}, {8, 13}
{3, 4}, {10, 9}, {8, 12}, {2, 6}, {5, 1}, {11, 13}, {7, 0}
{10, 0}, {12, 2}, {3, 5}, {9, 11}, {1, 13}, {4, 8}, {7, 6}
{6, 1}, {3, 5}, {11, 12}, {2, 10}, {7, 4}, {13, 8}, {0, 9}

ใช่ข้อตกลงแต่ละข้อมีวิธีแก้ไข

กฎระเบียบ

  • วิธีการแก้ปัญหาของคุณจะต้องทำงานในเวลาพหุนามในจำนวนถังต่อการย้าย ประเด็นก็คือใช้การวิเคราะห์พฤติกรรมที่ชาญฉลาด
  • อัลกอริทึมทั้งหมดจะต้องกำหนดขึ้น
  • ในกรณีที่เสมอกันจำนวนไบต์ที่สั้นที่สุดจะชนะ

2
จุดไหนที่หมายถึงปลายทางเมื่อมีเพียงช่องเดียวที่คุณสามารถย้ายแอปเปิ้ลไปได้
John Dvorak

จะเกิดอะไรขึ้นถ้าวิธีการแก้ปัญหาแบบ brute-force ของฉันทำงานในเวลาที่เหมาะสม มีเพียง 700 ล้านสถานะ - นับได้อย่างง่ายดายในไม่กี่นาที กำหนด "ระยะเวลาที่เหมาะสม"
John Dvorak

@ JanDvorak ต่อ "ประเด็นคืออะไร" - โทรดี นั่นไม่ได้เกิดขึ้นกับฉัน ฉันกำหนดที่เหมาะสมที่นี่เพื่อจะน้อยกว่าระยะเวลาที่จำเป็นในการบังคับเดรัจฉานวิธีการแก้;)
Orby

คำจำกัดความของคุณของ "สมเหตุสมผล" หมายความว่าเราควรใช้โซลูชัน brute-force ก่อนหรือไม่
John Dvorak

คำสั่งสุดท้ายของถังมีความสำคัญหรือไม่?
AMK

คำตอบ:


4

คะแนน: 448

ความคิดของฉันคือการเรียงลำดับพวกเขาอย่างต่อเนื่องเริ่มต้นด้วย 1 นี่ทำให้เรามีคุณสมบัติที่ดีที่เมื่อเราต้องการย้ายพื้นที่ไปยังตะกร้าก่อนหน้า / ถัดไปเรารู้ว่าแอปเปิ้ลสองตัวที่เราต้องย้าย - สูงสุด / ต่ำสุดหนึ่งตามลำดับ นี่คือการสลายการทดสอบ:

#1: 62     #6: 40
#2: 32     #7: 38
#3: 46     #8: 50
#4: 50     #9: 54
#5: 40    #10: 36

Total score: 448 moves

รหัสสามารถเล่นกอล์ฟได้มากขึ้น แต่คุณภาพของรหัสที่ดีขึ้นจะเป็นแรงบันดาลใจให้กับคำตอบเพิ่มเติม

C ++ (501 ไบต์)

#include <cstdio>
#define S(a,b) a=a^b,b=a^b,a=a^b;
int n=14,a[14],i,j,c,g,p,q;
int l(int x){for(j=0;j<n;++j)if(a[j]==x)return j;}
int sw(int d){
    p=l(0);q=p+d;
    if(a[q]*d>a[q^1]*d)q^=1;
    printf("%d,", a[q]);
    S(a[q],a[p])
}
int main(){
    for(;j<n;scanf("%d", a+j),j++);
    for(;++i<n;){
        c=l(i)/2;g=(i-1)/2;
        if(c-g){
            while(l(0)/2+1<c)sw(2);
            while(l(0)/2>=c)sw(-2);
            while(l(i)/2>g){sw(2);if(l(i)/2>g){sw(-2);sw(-2);}}
        }
    }
}

การปรับปรุงเพิ่มเติมอาจสลับไปที่ C และพยายามลดคะแนนโดยเริ่มจากค่าที่มีขนาดใหญ่ลง (และในที่สุดก็รวมการแก้ปัญหาทั้งสอง)


1
ซับสตริงของรหัสของคุณเป็นโปรแกรม C อยู่แล้ว โดยเฉพาะมันสามารถทำงานใน C โดยเพียงแค่ลบบรรทัดแรก
feersum

@feersum คุณพูดถูก ตอนแรกฉันมีรหัสเฉพาะ C ++ มากขึ้น แต่หลังจากนั้นเมื่อเปลี่ยนเป็น C ในใจดูเหมือนว่าฉันจะกำจัดมัน
yasen

คุณสามารถระบุว่าคุณได้เปลี่ยนรูปแบบการป้อนข้อมูลในโซลูชันของคุณเพื่อให้ชัดเจนยิ่งขึ้นสำหรับผู้ที่พยายามตรวจสอบหรือไม่
Orby

2

C, 426 448

แอปเปิ้ลนี้จะเรียงลำดับทีละครั้งตั้งแต่ 1 ถึง 13 ซึ่งคล้ายกับวิธีของ yasenยกเว้นเมื่อใดก็ตามที่มีโอกาสที่จะย้ายจำนวนที่มากขึ้นหรือน้อยลงมันจะใช้เวลา น่าเศร้าที่นี่ปรับปรุงประสิทธิภาพของปัญหาการทดสอบครั้งแรกเท่านั้น แต่เป็นการปรับปรุงเพียงเล็กน้อย ฉันทำผิดพลาดเมื่อใช้งานปัญหาการทดสอบ ดูเหมือนว่าฉันเพิ่งใช้วิธีของยาเซ็นอีกครั้ง

#1: 62    #6: 40
#2: 32    #7: 38
#3: 46    #8: 50
#4: 50    #9: 54
#5: 40    #10: 36

มันจะใส่โดยไม่ต้องใส่เครื่องหมายวงเล็บหรือเครื่องหมายจุลภาคเช่น

8 2 11 13 3 12 6 10 4 0 1 7 9 5

นี่คือรหัส golfed ที่มาที่ 423 ไบต์นับจำนวนบรรทัดใหม่ที่ไม่จำเป็น (อาจจะเพิ่มมากขึ้น แต่ฉันคาดหวังว่าคะแนนนี้จะแพ้):

#define N 7
#define F(x,y) for(y=0;y<N*2;y++)if(A[y]==x)break;
#define S(x,y) x=x^y,y=x^y,x=x^y;
#define C(x,y) ((A[x*2]==y)||(A[x*2+1]==y))
A[N*2],i,j,d,t,b,a,n,s,v,u,w,g;main(){for(;i<N*2;i++)scanf("%d",A+i);g=1;while
(!v){F(0,i);b=i/2;F(g,u);w=u/2;d=b<w?1:-1;n=(b+d)*2;a=(b+d)*2+1;if(A[n]>A[a])
S(n,a);t=d-1?a:n;printf("%d,",A[t]);S(A[i],A[t]);while(C((g-1)/2,g))g++;v=1;for
(j=0;j<N*2;j++)if(!C(j/2,(j+1)%(N*2)))v=0;}}

และรหัส ungolfed ซึ่งพิมพ์คะแนน:

#include <stdio.h>
#include <stdlib.h>

#define N 7

int apples[N*2];

int find(int apple)
{
    int i;
    for (i = 0; i < N*2; i++) {
        if (apples[i] == apple)
            return i;
    }    
}

void swap(int i, int j)
{
    int temp;
    temp = apples[i];
    apples[i] = apples[j];
    apples[j] = temp;
}

int contains(int bucket, int apple)
{
    if ((apples[bucket * 2] == apple) || (apples[bucket * 2 + 1] == apple))
        return 1;
    return 0;
}

int is_solved()
{
    int i, j;
    for (i = 0; i < N * 2; i++) {
        j = (i + 1) % (N * 2);
        if (!contains(i / 2, j))
            return 0;
    }
    return 1;
}

int main()
{
    int i, j, dir, bucket, max, min, score;
    int target_i, target_bucket, target;

    /* Read the arrangement */
    for (i = 0; i < N*2; i++) {
        scanf("%d ", apples + i);
    }

    target = 1;
    while (1) {

        i = find(0);
        bucket = i / 2;
        target_i = find(target);
        target_bucket = target_i / 2;

        /* Change the direction of the sort if neccesary */
        if (bucket < target_bucket) dir = 1;
        else dir = -1;

        /* Find the biggest and smallest apple in the next bucket */
        if (apples[(bucket + dir) * 2] < apples[(bucket + dir) * 2 + 1]) {
            min = (bucket + dir) * 2;
            max = (bucket + dir) * 2 + 1;
        } else {
            min = (bucket + dir) * 2 + 1;
            max = (bucket + dir) * 2;
        }

        /* If we're going right, move the smallest apple. Otherwise move the
           biggest apple */
        if (dir == 1) {
            printf("%d, ", apples[min]);
            swap(i, min);
            score++;
        } else {
            printf("%d, ", apples[max]);
            swap(i, max);
            score++;
        }

        /* Find the next apple to sort */
        while (contains((target - 1) / 2, target))
            target++;

        /* If we've solved it then quit */
        if (is_solved())
            break;
    }
    printf("\n");
    printf("%d\n", score);
}

2

Python 3 - 121

การดำเนินการนี้เป็นการค้นหาในเชิงลึกครั้งแรกด้วยการเพิ่มความลึกจนกว่าจะพบโซลูชัน มันใช้พจนานุกรมในการจัดเก็บสถานะการเยี่ยมชมเพื่อที่จะไม่เยี่ยมชมพวกเขาอีกครั้งเว้นแต่จะมีหน้าต่างความลึกที่สูงขึ้น เมื่อตัดสินใจว่าจะตรวจสอบสถานะใดจะใช้จำนวนองค์ประกอบที่ใส่ผิดที่เป็นฮิวริสติกและเยี่ยมชมเฉพาะรัฐที่ดีที่สุดเท่าที่จะเป็นไปได้ โปรดทราบว่าเนื่องจากลำดับขององค์ประกอบภายในที่ฝากข้อมูลของพวกเขาไม่สำคัญจึงคงคำสั่งซื้อไว้ในที่เก็บข้อมูล สิ่งนี้ทำให้ง่ายต่อการตรวจสอบว่าองค์ประกอบถูกใส่ผิดที่หรือไม่

อินพุตเป็นอาร์เรย์ของ int โดย int แรกเป็นจำนวนที่เก็บข้อมูล

ตัวอย่างเช่นสำหรับ # 8 (อันนี้ใช้เวลานานมากในการทำงานบนเครื่องของฉันคนอื่น ๆ เสร็จในไม่กี่วินาที):

c:\python33\python.exe apples.py 7 3 4 10 9 8 12 2 6 5 1 11 13 7 0

นี่คือผลลัพธ์ของชุดทดสอบ: # 1: 12, # 2: 12, # 3: 12, # 4: 12, # 5: 11, # 6: 11, # 7: 10, # 8: 14, # 9: 13, # 10: 14

นี่คือรหัส:

import sys    

BUCKETS = int(sys.argv[1])    

# cleans a state up so it is in order
def compressState(someState):
  for i in range(BUCKETS):
    if(someState[2*i] > someState[2*i + 1]):
      temp = someState[2*i]
      someState[2*i] = someState[2*i + 1]
      someState[2*i + 1] = temp
  return someState    

state = compressState([int(x) for x in sys.argv[2:]])
print('Starting to solve', state)
WINNINGSTATE = [x for x in range(1, BUCKETS*2 - 1)]
WINNINGSTATE.append(0)
WINNINGSTATE.append(BUCKETS*2 - 1)
maxDepth = 1
winningMoves = []
triedStates = {}    

# does a depth-first search
def doSearch(curState, depthLimit):
  if(curState == WINNINGSTATE):
    return True
  if(depthLimit == 0):
    return False
  myMoves = getMoves(curState)
  statesToVisit = []
  for move in myMoves:
    newState = applyMove(curState, move)
    tns = tuple(newState)
    # do not visit a state again unless it is at a higher depth (more chances to win from it)
    if(not ((tns in triedStates) and (triedStates[tns] >= depthLimit))):
      triedStates[tns] = depthLimit
      statesToVisit.append((move, newState[:], stateScore(newState)))
  statesToVisit.sort(key=lambda stateAndScore: stateAndScore[2])
  for stv in statesToVisit:
    if(stv[2] > statesToVisit[0][2]):
      continue
    if(doSearch(stv[1], depthLimit - 1)):
      winningMoves.insert(0, stv[0])
      return True
  return False    

# gets the moves you can make from a given state
def getMoves(someState):
  # the only not-allowed moves involve the bucket with the 0
  allowedMoves = []
  for i in range(BUCKETS):
    if((someState[2*i] != 0) and (someState[2*i + 1] != 0)):
      allowedMoves.append(someState[2*i])
      allowedMoves.append(someState[2*i + 1])
  return allowedMoves    

# applies a move to a given state, returns a fresh copy of the new state
def applyMove(someState, aMove):
  newState = someState[:]
  for i in range(BUCKETS*2):
    if(newState[i] == 0):
      zIndex = i
    if(newState[i] == aMove):
      mIndex = i
  if(mIndex % 2 == 0):
    newState[mIndex] = 0
  else:
    newState[mIndex] = newState[mIndex-1]
    newState[mIndex-1] = 0
  newState[zIndex] = aMove
  if((zIndex % 2 == 0) and (newState[zIndex] > newState[zIndex+1])):
    newState[zIndex] = newState[zIndex+1]
    newState[zIndex+1] = aMove
  return newState    

# a heuristic for how far this state is from being sorted
def stateScore(someState):
  return sum([1 if someState[i] != WINNINGSTATE[i] else 0 for i in range(BUCKETS*2)])    

# go!
while(True):
  triedStates[tuple(state)] = maxDepth
  print('Trying depth', maxDepth)
  if(doSearch(state, maxDepth)):
    print('winning moves are: ', winningMoves)
    break
  maxDepth += 1

ฉัน upvoted นี้เพราะมันมีประโยชน์ที่จะเห็นทางออกที่ดีที่สุด แต่สังเกตว่าสิ่งนี้ไม่ได้ทำงานในเวลาพหุนามในจำนวนถังต่อการย้ายตามคำถาม ฉันไม่เชื่อว่าอัลกอริทึมใด ๆ ที่สร้างทางออกที่ดีที่สุด (โดยทั่วไป) สามารถเรียกใช้ในเวลาพหุนาม
Orby

สำหรับปัญหาการทดสอบครั้งแรกโปรแกรมของคุณสร้าง 10, 8, 1, 12, 6, 7, 11, 3, 5, 13, 4, 9 ซึ่งไม่ใช่วิธีแก้ไขที่ถูกต้อง ฉันคิดว่าคุณอาจเข้าใจผิดคำถาม ขอให้สังเกตคำถามระบุว่า "คุณสามารถย้ายแอปเปิ้ลใดก็ได้จากที่เก็บข้อมูลหนึ่งไปยังที่ฝากข้อมูลที่อยู่ติดกัน" เช่นที่ฝากข้อมูลไปทางขวาหรือซ้ายของที่นี่ (ไม่ใช่ที่ฝากข้อมูลโดยพลการ)
Orby

โอ้ฉันพลาดข้อ จำกัด ของคำวิเศษณ์ทั้งหมด! หลังจากที่ฉันโพสต์สิ่งนี้ฉันมีความสงสัยซึ่งดุด่าว่าการ จำกัด เวลาทำงานก็ถูกละเมิดเช่นกัน ฉันไม่แน่ใจ 100% ขณะที่ฉันกำลังเขียนเพราะองค์ประกอบการเขียนโปรแกรมแบบไดนามิกของการหลีกเลี่ยงการทำซ้ำรัฐทำให้ฉันสับสน ขอบคุณสำหรับการโหวตแม้ว่ามันจะล้มเหลวในสองครั้ง นี่เป็นปริศนาที่สนุกและฉันจะดูว่าฉันสามารถหาคำตอบที่ดีกว่านี้ได้หรือไม่
RT
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.