Algorithm/Problem

마법사 상어와 파이어볼(BJ_G4_20056)

당진개발자 2024. 2. 24. 15:52

1. 문제 링크

https://www.acmicpc.net/problem/20056

 

20056번: 마법사 상어와 파이어볼

첫째 줄에 N, M, K가 주어진다. 둘째 줄부터 M개의 줄에 파이어볼의 정보가 한 줄에 하나씩 주어진다. 파이어볼의 정보는 다섯 정수 ri, ci, mi, si, di로 이루어져 있다. 서로 다른 두 파이어볼의 위치

www.acmicpc.net


 

2. 나의 코드

메모리: 40540kb
시간: 732ms
코드 길이: 4868B
시간 복잡도 : O(KM)
설명
- 먼저 모든 파이어볼을 이동시킨 후 합치기
- 합치는 과정에서 다음 객체와 같은지 다른지 비교
- 시작과 끝 인덱스를 사용하여 같은 위치에 있는 것들은 인덱스 리스트에 추가(끝나면 삭제)
- 추가해야 할 객체도 리스트에 추가 후 한번에 추가
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.StringTokenizer;

public class Main {

    static class FireBall implements Comparable<FireBall> {
        int r, c, m, s, d;

        public FireBall(int r, int c, int m, int s, int d) {
            this.r = r;
            this.c = c;
            this.m = m;
            this.s = s;
            this.d = d;
        }

        @Override
        public int compareTo(FireBall o) {
            if (this.r != o.r)
                return this.r - o.r;
            return this.c - o.c;
        }
    }

    static int[] dr = {-1, -1, 0, 1, 1, 1, 0, -1};
    static int[] dc = {0, 1, 1, 1, 0, -1, -1, -1};
    static int N, M, K;
    static List<FireBall> fireBalls;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());
        K = Integer.parseInt(st.nextToken());
        fireBalls = new ArrayList<FireBall>();
        for (int i = 0; i < M; i++) {
            st = new StringTokenizer(br.readLine());
            fireBalls.add(new FireBall(Integer.parseInt(st.nextToken()), Integer.parseInt(st.nextToken()),
                    Integer.parseInt(st.nextToken()), Integer.parseInt(st.nextToken()),
                    Integer.parseInt(st.nextToken())));
        }
        while (K-- > 0) {
            move();
            fireBalls.removeIf(f -> f.m <= 0);
        }

        int result = 0;
        for (FireBall f : fireBalls) {
            result += f.m;
        }
        System.out.println(result);
    }

    // 이동
    private static void move() {
        for (FireBall fireBall : fireBalls) {
            int tempR = (fireBall.r + N + dr[fireBall.d] * (fireBall.s % N)) % N;
            int tempC = (fireBall.c + N + dc[fireBall.d] * (fireBall.s % N)) % N;
            fireBall.r = tempR;
            fireBall.c = tempC;
        }
        // 같은 위치 합치기
        union();
    }

    private static void union() {
        int unionCnt = 1;
        int s = 0, e = 0;
        List<FireBall> addFireBallList = new ArrayList<>(); // 추가할 파이어볼 객체
        List<Integer> removedIdxList = new ArrayList<>();    // 삭제할 인덱스
        Collections.sort(fireBalls);
        for (int i = 0; i < fireBalls.size() - 1; i++) {
            FireBall now = fireBalls.get(i);
            FireBall next = fireBalls.get(i + 1);
            // 다음거랑 같으면?
            if (now.r == next.r && now.c == next.c) {
                next.m += now.m;
                next.s += now.s;
                unionCnt++;
                e = i + 1;  // 다음거랑 같으면 인덱스 증가
                removedIdxList.add(i);
                if (i == fireBalls.size() - 2) {
                    removedIdxList.add(i + 1);
                    int newM = next.m / 5;
                    int newS = next.s / unionCnt;
                    int newD;
                    if (EvenAddCheck(s, e)) newD = 0;
                    else newD = 1;
                    for (int j = 0; j < 4; j++) {
                        addFireBallList.add(new FireBall(now.r, now.c, newM, newS, newD));
                        newD += 2;
                    }
                }
            } else { // 다음거랑 다르면?
                if (unionCnt > 1) {
                    int newM = now.m / 5;
                    int newS = now.s / unionCnt;
                    int newD;
                    if (EvenAddCheck(s, e)) newD = 0;
                    else newD = 1;
                    for (int j = 0; j < 4; j++) {
                        addFireBallList.add(new FireBall(now.r, now.c, newM, newS, newD));
                        newD += 2;
                    }
                    removedIdxList.add(i);
                }
                s = i + 1;
                unionCnt = 1;
            }
        }
        int cnt = 0;
        for (int idx : removedIdxList) {
            fireBalls.remove(idx - cnt++);
        }
        fireBalls.addAll(addFireBallList);
    }

    // 삭제할 것들이 모두 홀수인지 짝수인지 확인
    private static boolean EvenAddCheck(int s, int e) {
        int odd = 0;
        int even = 0;
        for (int i = s; i <= e; i++) {
            if (fireBalls.get(i).d % 2 == 0) even++;
            else odd++;
        }
        if (odd == 0 || even == 0) return true;
        return false;
    }
}

 


 

3. 정답 코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.StringTokenizer;

public class Main {

	static int N, M, K;
	static int[] dr = {-1, -1, 0, 1, 1, 1, 0, -1};
	static int[] dc = {0, 1, 1, 1, 0, -1, -1, -1};
	static ArrayList<Fireball> fireballs;
	
	static class Fireball implements Comparable<Fireball> {
		int r, c, m, d, s, num; // num은 2차원 배열의 몇번째 칸인지를 의미함

		public Fireball(int r, int c, int m, int d, int s) {
			this.r = r;
			this.c = c;
			this.m = m;
			this.d = d;
			this.s = s;
		}

		@Override
		public int compareTo(Fireball o) {
			return this.num - o.num;
		}
	}
	
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringBuilder sb = new StringBuilder();
        StringTokenizer st;
        
        // 초기 세팅
        st = new StringTokenizer(br.readLine());
        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());
        K = Integer.parseInt(st.nextToken());
        fireballs = new ArrayList<>();
        
        for (int i = 0; i < M; i++) {
        	st = new StringTokenizer(br.readLine());
        	int r = Integer.parseInt(st.nextToken());
        	int c = Integer.parseInt(st.nextToken());
        	int m = Integer.parseInt(st.nextToken());
        	int s = Integer.parseInt(st.nextToken());
        	int d = Integer.parseInt(st.nextToken());
        	fireballs.add(new Fireball(r, c, m, d, s));
        }
        
        play();
        
        int answer = 0;
        for (Fireball fb : fireballs) {
        	answer += fb.m;
        }
        System.out.println(answer);
    }
    
    private static void play() {
    	// K번동안 반복
    	for (int time = 0; time < K; time++) {
    		for (int i = 0; i < fireballs.size(); i++) {
    			Fireball now = fireballs.get(i);
    			// 속력 s만큼 방향 d로 이동
    			int nr = now.r + dr[now.d] * (now.s % N);
    			int nc = now.c + dc[now.d] * (now.s % N);
    			
    			if (nr > 0) {
    				nr %= N;
    			} else if (nr < 0) {
    				nr += N;
    			}
    			if (nc > 0) {
    				nc %= N;
    			} else if (nc < 0) {
    				nc += N;
    			}
    			
    			now.r = nr;
    			now.c = nc;
    			now.num = nr * N + nc;
    		}
    		
    		// 위치순으로 파이어볼 정렬
    		Collections.sort(fireballs);
    		
    		for (int i = 0; i < fireballs.size() - 1; i++) {
    			Fireball now = fireballs.get(i);
    			Fireball next = fireballs.get(i + 1);
    			
    			// 2개 이상의 파이어볼이 있는 칸을 찾은 경우
    			if (now.num == next.num) {
    				int num = now.num;
    				boolean oddFlag = false;
    				boolean evenFlag = false;
    				int mSum = 0;
    				int sSum = 0;
    				int cnt = 0;
    				
    				// 같은 칸에 있는 파이어볼들을 찾아서 로직을 수행
    				while (i < fireballs.size() && num == fireballs.get(i).num) {
    					Fireball fb = fireballs.get(i);
    					mSum += fb.m;
    					sSum += fb.s;
    					if (fb.d % 2 == 0) {
    						evenFlag = true;
    					} else {
    						oddFlag = true;
    					}
    					fireballs.remove(i);
    					cnt++;
    				}
    				mSum /= 5;
    				sSum /= cnt;
    				
    				// 질량이 1이상이라면
    				if (mSum > 0) {
    					// 합쳐지는 파이어볼의 방향이 모두 홀수이거나 짝수이면
    					if (!oddFlag || !evenFlag) {
    						for (int j = 0; j < 7; j += 2) {
    							fireballs.add(i, new Fireball(num / N, num % N, mSum, j, sSum));
    						}
    					// 그렇지 않다면
    					} else {
    						for (int j = 1; j < 8; j += 2) {
    							fireballs.add(i, new Fireball(num / N, num % N, mSum, j, sSum));
    						}
    					}
    					i += 3;
    					
    				// 질량이 0이라면 파이어볼이 사라지므로 기존 index를 다시 탐색해야함
    				} else {
    					i--;
    				}
    			}
    		}
    	}
    }
}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.StringTokenizer;

public class Main {

	static int N, M, K;
	static int[] dr = {-1, -1, 0, 1, 1, 1, 0, -1};
	static int[] dc = {0, 1, 1, 1, 0, -1, -1, -1};
	static ArrayList<Fireball> fireballs;
	
	static class Fireball implements Comparable<Fireball> {
		int r, c, m, d, s, num; // num은 2차원 배열의 몇번째 칸인지를 의미함

		public Fireball(int r, int c, int m, int d, int s) {
			this.r = r;
			this.c = c;
			this.m = m;
			this.d = d;
			this.s = s;
		}

		@Override
		public int compareTo(Fireball o) {
			return this.num - o.num;
		}
	}
	
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringBuilder sb = new StringBuilder();
        StringTokenizer st;
        
        // 초기 세팅
        st = new StringTokenizer(br.readLine());
        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());
        K = Integer.parseInt(st.nextToken());
        fireballs = new ArrayList<>();
        
        for (int i = 0; i < M; i++) {
        	st = new StringTokenizer(br.readLine());
        	int r = Integer.parseInt(st.nextToken());
        	int c = Integer.parseInt(st.nextToken());
        	int m = Integer.parseInt(st.nextToken());
        	int s = Integer.parseInt(st.nextToken());
        	int d = Integer.parseInt(st.nextToken());
        	fireballs.add(new Fireball(r, c, m, d, s));
        }
        
        play();
        
        int answer = 0;
        for (Fireball fb : fireballs) {
        	answer += fb.m;
        }
        System.out.println(answer);
    }
    
    private static void play() {
    	// K번동안 반복
    	for (int time = 0; time < K; time++) {
    		for (int i = 0; i < fireballs.size(); i++) {
    			Fireball now = fireballs.get(i);
    			// 속력 s만큼 방향 d로 이동
    			int nr = now.r + dr[now.d] * (now.s % N);
    			int nc = now.c + dc[now.d] * (now.s % N);
    			
    			if (nr > 0) {
    				nr %= N;
    			} else if (nr < 0) {
    				nr += N;
    			}
    			if (nc > 0) {
    				nc %= N;
    			} else if (nc < 0) {
    				nc += N;
    			}
    			
    			now.r = nr;
    			now.c = nc;
    			now.num = nr * N + nc;
    		}
    		
    		// 위치순으로 파이어볼 정렬
    		Collections.sort(fireballs);
    		
    		for (int i = 0; i < fireballs.size() - 1; i++) {
    			Fireball now = fireballs.get(i);
    			Fireball next = fireballs.get(i + 1);
    			
    			// 2개 이상의 파이어볼이 있는 칸을 찾은 경우
    			if (now.num == next.num) {
    				int num = now.num;
    				boolean oddFlag = false;
    				boolean evenFlag = false;
    				int mSum = 0;
    				int sSum = 0;
    				int cnt = 0;
    				
    				// 같은 칸에 있는 파이어볼들을 찾아서 로직을 수행
    				while (i < fireballs.size() && num == fireballs.get(i).num) {
    					Fireball fb = fireballs.get(i);
    					mSum += fb.m;
    					sSum += fb.s;
    					if (fb.d % 2 == 0) {
    						evenFlag = true;
    					} else {
    						oddFlag = true;
    					}
    					fireballs.remove(i);
    					cnt++;
    				}
    				mSum /= 5;
    				sSum /= cnt;
    				
    				// 질량이 1이상이라면
    				if (mSum > 0) {
    					// 합쳐지는 파이어볼의 방향이 모두 홀수이거나 짝수이면
    					if (!oddFlag || !evenFlag) {
    						for (int j = 0; j < 7; j += 2) {
    							fireballs.add(i, new Fireball(num / N, num % N, mSum, j, sSum));
    						}
    					// 그렇지 않다면
    					} else {
    						for (int j = 1; j < 8; j += 2) {
    							fireballs.add(i, new Fireball(num / N, num % N, mSum, j, sSum));
    						}
    					}
    					i += 3;
    					
    				// 질량이 0이라면 파이어볼이 사라지므로 기존 index를 다시 탐색해야함
    				} else {
    					i--;
    				}
    			}
    		}
    	}
    }
}

 


 

4. 배운 점

- 이동을 처리하는 부분에서 많은 고생을 했다.

- 리스트를 사용할 경우 Stream을 많이 사용해보자!