백준 알고리즘 10875 : 뱀 (시간초과)

2023. 10. 13. 17:59·BaekJoon
반응형

일단 뱀을 정의 해보자

더보기
	public class Bam
	{
		public int x, y, moveCount, angle;

		public List<SnakeSegment> body = new List<SnakeSegment>();

		public Bam(int zeroPoint)
		{
			angle = 90;
			x = y = zeroPoint;
			moveCount = 0;
		}

		public void Move()
		{
			SnakeSegment segmet = new SnakeSegment(x, y);
			body.Add(segmet);

			switch(angle)
			{
				case 0: y += 1; break;
				case 90: x += 1; break;
				case 180: y -= 1; break;
				case 270: x -= 1; break;
			}
			moveCount++;
		}
	}

뱀은 자기 자신의 위치(x,y)와 움직인 횟수(moveCount), 회전각(angle)과 몸통인(body)를 가지고 있다.

문제에서 뱀은 처음에 격자판의 중앙에 있고 머리의 방향은 항상 오른쪽이라고 작성되어 있어 생성자에서 이 부분을 셋팅해주었다.

뱀은 스스로 이동할 수 있으며, 이동 할 때 마다 이동한곳 만큼 몸통이 증가한다.

 

Switch문을 어떻게 간단하게 표현할까 고민하면서 삽질을 많이 했다... 문제를 성고하고 나서 다른 사람들은 어떻게 했는지 찾아봐야겠다. 10.13 기준

 

뱀의 몸통

더보기
	public class SnakeSegment
	{
		public int X, Y;

		public SnakeSegment(int x, int y)
		{
			X = x;
			Y = y;
		}
	}

처음엔 Bam 클래스에 List<Tuple<int,int>> 변수를 선언해 몸통을 표현했는데 튜플을 활용하기 좀 번거로워서 좌표값만 가지고 있는 몸통 클래스를 만들었다. 

 

입출력을 하는 메인 함수

더보기
		static int size, rotation, plate, rotateNum =0;
		static List<char[]> rotationValue = new List<char[]>();
        
		public static void Main()
		{

			size = int.Parse(Console.ReadLine());
			rotation = int.Parse(Console.ReadLine());

			for (int i = 0; i < rotation; i++)
			{
				char[] input = Console.ReadLine().ToCharArray();
				
				rotationValue.Add(input);
			}

			plate = 2 * size;

			Bam bam = new Bam(size);

			while(!IsDead(bam, plate))
			{
				Rotate(bam);
				bam.Move();
				rotateNum++;
			}

			Console.WriteLine(bam.moveCount);
		}

입력 첫 줄에는 L, 두 번째 줄엔 몇번 회전할지 나타내는 정수로 이 값만큼 추가로 입력을 더 받아야 해 for문을 돌려 딕셔너리에 저장한다.

격자판인 plate는 문제에서 2L + 1로 주어져 2차원 배열로 만들어보았지만 계산만 복잡해져 2 * L로 계산 ( 0도 포함하기에 +1은 하지 않았다)

 

뱀 객체를 생성 매개변수인 zeroPoint는 L을 넣어준다( 격자판의 계산식은 2L + 1로 항상 홀수다, 이 격자판의 중앙은 L,L이다 내 계산식에서는 0도 포함하기 때문) 

 

뱀이 죽을 때 까지 while문을 돌며 이동과 회전을 반복후 죽은 뒤 이동횟수를 출력한다. 

 

뱀이 죽었는지 판별

더보기

 

public static bool IsDead(Bam bam, int plates)
{
    if (bam.x < 0 || bam.y < 0) return true;
    else if (Math.Abs(bam.x) > plates || Math.Abs(bam.y) > plates) return true;

    foreach(SnakeSegment segment in bam.body)
    {
        if (segment.X == bam.x && segment.Y == bam.y) return true;
    }

    return false;
}

죽었을시 true를 반환하고 살아있다면 false를 반환하는 함수다. 

뱀의 머리가 격자판을 나가면 죽는다. 따라서 뱀의 좌표가 0보다 작을 수 없고, 격자판보다 클 수도 없다. 

 

뱀이 자기 자신의 몸에 닿으면 죽기에 body를 순회하며 현재 위치가 몸과 일치하는지 순회하며 확인한다.

 

뱀 회전

더보기
public static void Rotate(Bam bam)
{
    if (rotationValue == null) return;

    int rotateTime = (int)Char.GetNumericValue( rotationValue[0][0]);

    if (rotateTime.Equals(rotateNum))
    {
        if (rotationValue[0][2] =='L')
            bam.angle -= 90;
        else bam.angle += 90;

        rotateNum = 0;
        rotationValue.RemoveAt(0);
    }

    switch(bam.angle)
    {
        case 360: bam.angle = 0; break;
        case -90: bam.angle = 270; break;
    }
}

회전을 해야할 이동횟수를 나타내는 rotateTime변수와 rotateNum 변수를 비교해 같다면 

뒤의 문자를 비교해 L이면 왼쪽 R은 오른쪽으로 회전 후 rotateNum을 0으로 초기화하고 해당 회전값을 삭제한다.

수직 방향을 0도로 기준으로 해 360도를 사용하므로 360도는 0으로, 0도에서 왼쪽으로 회전 시 각이 음수로 될 수 있어 270도로 변환한다.

 

예제1,2번을 둘 다 테스트 해보았고 문제 출제자가 의도한대로 잘 된것 같은데 왜 실패했는지 알 수가 없다....

 

2023_10_13 실패


2023_10_17 시간초과

백준 홈페이지에서 문제를 다시 보다가 질문게시판을 찾게 되었다.

문제에서 제공된 예시말고 다른 사람들의 테스트 케이스를 입력하였더니

에러가 발생했다. 

딕셔너리는 비어있지만 Null은 아니다 

if (rotationValue == null) return;

이와 같은 에러처리는 잘못되었다. 

if (rotationValue.Count < 1) return;

이와같이 변경하고 들뜬 마음에 재제출을 해보았다. 하지만 실패... 


Move 함수에 로그를 찍어가며 어느 부분이 문제인지 확인해보았다. Bam클래스에 기준점인 int point 추가함. 

Console.WriteLine($"{body[moveCount-1].X- point} {body[moveCount - 1].Y - point} -> {this.x- point} {y-point}");

Rotate 함수에도 회전을 할 때마다 로그가 찍히도록 하고 테스트를 진행해보니 회전해야 할 이동횟수가 아스키 코드의 숫자범위(9)를 초과하면 회전 해야 할 때가 아닌데 회전하는것을 발견했다. 

 

정수와 문자를 어떻게 저장해 활용할지 고민하다가 처음에 생각했던 딕셔너리를 사용하기로 마음먹었다.

 

컴파일러에서 경고뜨는것이 보기 싫어서 입력 부분도 변경해주었는데 

가장 중요한 부분은 새롭게 추가한 변수인 rotateCount이다. 

입력받은 문자의 정수를 계속 추가해 몇번째에 회전을 해야하는지를 딕셔너리에 추가 할 수 있다. 

더보기
static Dictionary<int, char> rotationValue = new Dictionary<int, char>();
public static void Main()
{
    int.TryParse(Console.ReadLine(), out size);
    int.TryParse(Console.ReadLine(), out rotation);

    int rotateCount = 0;
    for (int i = 0; i < rotation; i++)
    {
        string? inputValue = Console.ReadLine();
        if (inputValue == null) return;
        string[] input = inputValue.Split();

        int inputCount = int.Parse(input[0]);
        rotateCount += inputCount;
        rotationValue.Add(rotateCount, char.Parse(input[1])) ;
        
        plate = 2 * size;
        
        Bam bam = new Bam(size);

        while(!IsDead(bam, plate))
        {
            if (rotationValue.ContainsKey(rotateNum) && rotationValue.Count < 1)
                Rotate(bam);
            bam.Move();
            rotateNum++;
        }

        Console.WriteLine(bam.moveCount);
}

 

List 자료형에서 Dictionary로 바꿔 Rotate함수도 깔끔해졌고, 예외처리 부분도 밖으로 빼 함수 이름에 맞게끔 회전만 담당하도록 하였다.

public static void Rotate(Bam bam)
{
    char dir = rotationValue[rotateNum];
    if (dir.Equals('L')) bam.angle -= 90;
    else bam.angle += 90;

    if (bam.angle == 360) bam.angle = 0;
    else if (bam.angle == -90) bam.angle = 270;
}

테스트 후 제출을 하였는데 이번에는 시간초과가 발생했다

 

IsDead함수에서 body를 순회하는 부분이 가장 오래 걸릴거 같아 이와 같이 변경했지만 결과는 같았다...

더보기
public class Bam
{
    public int x, y, moveCount, angle, point;

    public List<string> body = new List<string>();

    public Bam(int zeroPoint)
    {
        angle = 90;
        x = y = point = zeroPoint;
        moveCount = 0;
    }

    public void Move()
    {
        string bodyPoint = x.ToString() + y.ToString();
        body.Add(bodyPoint);
        switch (angle)
        {
            case 0: y += 1; break;
            case 90: x += 1; break;
            case 180: y -= 1; break;
            case 270: x -= 1; break;
        }
        moveCount++;
    }
}
public static bool IsDead(Bam bam, int plates)
{
    if (bam.moveCount < 1) return false;
    if (bam.x < 0 || bam.y < 0 || bam.x > plates || bam.y > plates)
        return true;

    else if (bam.body.Contains(bam.x.ToString() + bam.y.ToString()))
        return true;

    return false;
}

시간초과 문제를 해결하기 위해선 내 생각엔 예측을 해야할 것 같다. 

100000000
5
100000000 L
100000000 L
200000000 L
199999999 L
199999999 L

이 테스트 예제를 입력 했더니 몇분이 되어도 출력이 나오지 않았다. 

 

저작자표시 비영리 변경금지 (새창열림)

'BaekJoon' 카테고리의 다른 글

JavaFestival23번 문제 C#으로 풀어보기  (0) 2024.04.24
백준 알고리즘 2754 : 학점계산  (0) 2023.09.26
백준 알고리즘 25206번 : 너의 평점은  (2) 2023.09.26
백준 알고리즘 1264번 : 모음의 개수  (0) 2023.09.15
백준 알고리즘 1330번 : 두 수 비교하기  (0) 2023.09.11
'BaekJoon' 카테고리의 다른 글
  • JavaFestival23번 문제 C#으로 풀어보기
  • 백준 알고리즘 2754 : 학점계산
  • 백준 알고리즘 25206번 : 너의 평점은
  • 백준 알고리즘 1264번 : 모음의 개수
코샵
코샵
나의 코딩 일기장
    반응형
  • 코샵
    끄적끄적 코딩 공방
    코샵
    • 분류 전체보기 (672) N
      • 상품 추천 (178) N
      • MongoDB (4)
      • 하드웨어 (11)
      • 일기장 (4)
      • Unity (138)
        • Tip (41)
        • Project (1)
        • Design Pattern (8)
        • Firebase (6)
        • Asset (2)
      • 파이썬 (12)
        • Basic (41)
        • OpenCV (8)
        • Pandas (15)
        • PyQT (3)
        • SBC(Single Board Computer) (1)
        • 크롤링 (14)
        • Fast API (29)
        • Package (6)
      • Linux (4)
      • C# (97)
        • Algorithm (11)
        • Window (7)
      • TypeScript (50)
        • CSS (10)
      • Git (11)
      • SQL (5)
      • Flutter (10)
        • Tip (1)
      • System (1)
      • BaekJoon (6)
      • Portfolio (2)
      • MacOS (1)
      • 유틸리티 (1)
      • 서비스 (6)
      • 자동화 (3)
      • Hobby (10)
        • 물생활 (10)
        • 식집사 (0)
  • 인기 글

  • 태그

    list
    쇼핑몰리뷰
    믈레코비타멸균우유
    devlife
    programming101
    유니티
    C#
    unity
    카페24리뷰
    리스트
    스크립트 실행 순서
    리뷰관리
    셀레니움
    programmerlife
    ipcamera
    긴유통기한우유
    codingtips
    appdevelopment
    리뷰이관
    라떼우유
    카페24리뷰이관
    스마트스토어리뷰
    상품 리뷰 크롤링
    파이썬
    rtsp
    Python
    codingcommunity
    스크립트 실행
    cv2
    learntocode
  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
코샵
백준 알고리즘 10875 : 뱀 (시간초과)
상단으로

티스토리툴바