https://programmers.co.kr/learn/courses/30/lessons/12978

 

코딩테스트 연습 - 배달 | 프로그래머스

5 [[1,2,1],[2,3,3],[5,2,2],[1,4,2],[5,3,1],[5,4,2]] 3 4 6 [[1,2,1],[1,3,2],[2,3,2],[3,4,3],[3,5,2],[3,5,3],[5,6,1]] 4 4

programmers.co.kr

 

[주의할 조건]

마을의 개수 N은 1 이상 50 이하의 자연수입니다.

두 마을 a, b를 연결하는 도로는 여러 개가 있을 수 있습니다.

a, b(1 ≤ a, b ≤ N, a != b)

 

[구하는 값]

1번 마을에 있는 음식점이 K 이하의 시간에 배달이 가능한 마을의 개수를 return 하면 됩니다.

 

[생각의 흐름]

1번 지역에서 인접한 곳으로 이동하며 지나온 거리를 증가시키다가, K를 넘으면 이동을 하지 않는 방법으로 푼다.

dfs,bfs 무엇을 써도 무방하다.

dfs로 하면 한 마을에 인접한 마을의 최대 수가 50인데 한번 방문한 곳은 방문하지 않기 때문에

시간복잡도는 50x49x48...=250만.

백트래킹시에 방문체크 해제를 하지 않아야 재방문을 피한다.

 

인접마을을 표현할 땐 인접배열과 인접리스트 중에서 인접리스트를 썼다.

a->b로 가는 경로가 여러개가 있을 수도 있기 때문이다. 인접배열을 쓴다면

[a][b]에는 가중치가 하나만 저장된다. (->사실 최소로 갱신하면 되는데 이땐 몰랐다)
그래서 인접리스트로 표현을 했고, dfs 탐색시에 방문했던 마을이라고 무조건 안가는 것이 아니라

그 방문체크값을 확인해서 이전보다 빠른 경로로 왔다고 판단될 때에만 그곳을 가도록 만든다.
그렇기때문에 탐색이 진행되기 위해선 방문체크값의 초기값은 큰 수이어야 하며

이러한 탐색 방식을 다익스트라라고 한다.

 

그렇게 기존에 방문한 비용에 비해 절약된 비용으로 다른 노드를 갈 수 있다는 것에 의미가 있다.

 

 

[코드]

import java.util.ArrayList; 
import java.util.Arrays; 

class Solution {  //프로그래머스는 static으로 모두 선언해서 하지 않고 인스턴스 solution으로 돌리므로 다 인스턴스 변수여야 한다.   
   int ans=1;    //즉 메인 만들어서 돌려볼때 인스턴스로 접근해야 한다는 뜻. 대신 메인에서 무슨 기능 넣으면 안됨,,초기지역도 포함해야되서 1로 시작 
   int visit[]=new int[51];  //전역변수 할거는 전역변수로! 
   int K,N; 
   ArrayList<ArrayList<int[]>> adj=new ArrayList<ArrayList<int[]>>(); 
   public static void main(String[] args) { 
      int[][] arr= { 
            {1,2,1},{2,3,3},{5,2,2},{1,4,2},{5,3,1},{5,4,2} 
      }; 
      Solution m=new Solution(); 
      System.out.print(m.solution(5,arr,3)); 
   } 

    public int solution(int N, int[][] road, int K) {   
       Arrays.fill(visit,Integer.MAX_VALUE);  //수에 관한건 Integer에서, 배열 관련한 함수는 Arrays에! 
       this.N=N; 
       this.K=K; 
        for(int i=0;i<51;i++)  
           adj.add(new ArrayList<int[]>());  //이중어레 초기화 필요 
         
       for(int i=0;i<road.length;i++) { 
           adj.get(road[i][0]).add(new int[]{road[i][1],road[i][2]});  //int[]는 Integer처럼 쓰인다  
           adj.get(road[i][1]).add(new int[]{road[i][0],road[i][2]}); 
        } 
         
        dfs(1,0); 
        return ans; 
    } 
     
    public void dfs(int cur,int len) { 
       visit[cur]=len; 
        
       for(int[] nx:adj.get(cur)) { 
          if(nx[1]+len>K||visit[nx[0]]<nx[1]+len)  //다익스트라. 같은 지점 간다면 더 짧을때만 갈 수 있다. 
             continue;   //왜냐?a~b사이 가중치 여러개 있을 수 있어서 가봤다고 아예 안가면 안됨 
          if(visit[nx[0]]==Integer.MAX_VALUE)  //0이 안갔단 표시가 아님 
             ans++; 
          dfs(nx[0],nx[1]+len); 
       }    
    } 
} 

 




 

'PS' 카테고리의 다른 글

백준 17349 1루수가 누구야  (0) 2019.08.14
백준 17090 미로 탈출하기  (0) 2019.08.14
백준 13251 조약돌 꺼내기  (0) 2019.08.13

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

 

17349번: 1루수가 누구야

(1 2)가 거짓말이라면, 선수 2가 1루수라는 주장과 1루수가 아니라는 주장이 동시에 존재하여 모순이다. (0 4)가 거짓말인 경우도, 마찬가지의 이유로 모순이다. (0 2)가 거짓말인 경우, 선수 2를 유일한 1루수로 특정할 수 있다. 다른 경우는 없다.

www.acmicpc.net

[주의할 조건]

정확히 한 명이 거짓말을 하고 있다.

정확히 한 명이 1루수로 정해지는 경우만 등번호를 출력한다.

 

[구하는 값]

1루수로 정해진 선수의 등번호

 

[생각의 흐름]

이런 논리적 흐름으로 결론을 도출하는 문제는 조건 하나가 주어지면

그 조건으로 결정되는 것들은 모두 체크를 해주어야 한다. (구슬찾기 문제가 생각난다..)

이 문제에선 여러 주장들을 처음에 주는데, 각각의 선수들이 거짓말을 하는 모든 경우를 계산해봐야 한다. 

 

그래서 나는 def()를 통해서 포문으로 

1번 선수가 거짓말을 했을 때-> 누가 1루수로 정해지는지 를 lie(1)로 반환.

2번 선수가 거짓말을 했을 때-> 누가 1루수로 정해지는지 를 lie(2)로 반환.

...

이 순서로 검사를 했다. 누가 거짓말을 하든 한 사람을 특정해야 하므로 

lie() 값이 바뀌면 def()는 -1로, 바뀌지 않고 무사히 종료되면 def()는 그 값으로 종료된다.

 

 

lie(i)는 다음과 같이 동작한다.

<1> i의 주장만 반대로 선수의 1루수 여부를 배열에 체크한다.(i가 누군가가 참이라고 주장했으면

나머지는 모두 1루수가 아니라는 체크도 해줘야 한다.)

<2>나머지 선수는 모두 참인 것을 가정하여 선수들의 1루수 여부를 배열에 체크한다.(마찬가지로, 참을 주장하는 경우 나머지가 모두 1루수가 아니라는 것을 체크해줘야 한다)

...

이 과정을 거치면서 lie(i)는 중간에 모순을 발견하거나, 모순 없이 완료가 된다.

 

//모순을 발견한 경우

<2>를 실행하는 도중에 x로 체크되어 있던 곳에 o를 체크해야하는 상황이 오거나 그 반대일 경우, 모순이다.

이런 경우에는 lie(i)를 -1로 나오고 쓸모없는 가정이라 무시하고 다음 lie를 계산한다

 

//모순이 없는 경우

모순없이 실행이 완료되었으면 누가 1루수로 모아졌는지 확인을 해야한다.  이때 선수들의 상태값은 참(o),거짓(x),미정(?)으로 존재한다. 다음이 그 예시이다.

 

oxxxxxx

oxx?xx?

?xxxxxx

 

o를 기준으로 생각하자.

 

o가 2개 이상 나오는 경우? 는 불가능하다. <2>를 실행하며 모순으로서 나갔을 것이다.

o가 1개인 경우? 지금은 이미 누가 거짓말을 하고 누가 진실을 얘기했는지 정해진 상태이기 때문에, 더이상 무언가가 거짓일 가능성은 생각하지 않으므로,그 o를 믿어도 된다. 1루수가 그 o로 정해진다.

o가 0개인 경우? 예를 들어서 모두의 주장이 '2번선수는 1루수가 아니다'일 경우이다. 정보가 하나밖에 없으므로 누군가를 o라고 할 수도 없지만 x라고 할 수도 없다. 즉 1,3,4...가 1루수일 가능성이 있는 것이다. 모순이 없었기 때문이 이 가능성은 유효하다. 이런 경우, 특정지을 수 있는 인물이 2명 이상이 되므로 이 경우 lie(i)를 넘어서 def()를 -1로 나와야한다. 

 

!!!누가 거짓말을 하든 1루수가 하나로 특정되어야 한다. 나는 이 부분을 착각해서 몇 번 틀렸었다!!!

 

 

다시 def()로 돌아와서,

 

모든 i=1,2,3,...에 대해서 lie(i)가 한 사람을 특정하면 그 값으로 답을 반환하고

 

어떤 lie(i)가 모순없이 두 사람 이상을 특정하면 -1로 값을 반환하고

 

어떤 lie(i)가 모순이면 그 값은 버려진다.

 

 

->코드가 좀 더럽다ㅠ

#define _CRT_SECURE_NO_WARNINGS
#include<iostream> 
#include<cstring>
#include<vector>
using namespace std;
int rusu[10];
int talk[10][2];
int lie(int num);
int def();
int main() {

	freopen("Text.txt", "r", stdin);
	for (int i = 1; i <= 9; i++) {
		cin >> talk[i][0] >> talk[i][1];
	}
	
	cout << def();
	return 0;
}
int def() {
	int tmp;
	int ans = -1;
	for (int i = 1; i <= 9; i++) {
		memset(rusu, 0, sizeof(rusu));
		tmp = lie(i);

		if (tmp == -2)
			return -1;
		if (tmp >0 && ans == -1) {
			ans = tmp;
		}
		else if (tmp > 0 && ans>0) {
			if (ans == tmp)
				continue;
			else
				return -1;
		}
	}
	return ans;
}
int lie(int num) {

	if (talk[num][0] == 1)
		rusu[talk[num][1]] = -1;
	else {
		rusu[talk[num][1]] = 1;
		for (int i = 1; i <= 9; i++) {   //하나 결정되면 완벽히 차단...결정될 수 있는거 지금 다 결정한다는  뜻
			if(i!= talk[num][1])
				rusu[i] = -1;
		}
	}
	for (int i = 1; i <= 9; i++) {
		if (i == num)
			continue;
		if (talk[i][0] == 1) {
			if (rusu[talk[i][1]] == -1)
				return -1;
			rusu[talk[i][1]] = 1;
			for (int j = 1; j <= 9; j++) {
				if (j != talk[i][1]) {
					if (rusu[j] == 1)
						return -1;
					rusu[j] = -1;
				}
			}
		}
		else {
			if (rusu[talk[i][1]] == 1)
				return -1;
			rusu[talk[i][1]] = -1;
		}
	}
	vector<int> one;
	vector<int> zero;
	for (int i = 1; i <= 9; i++) {
		if (rusu[i] == 1)
			one.push_back(i);
		else if (rusu[i] == 0)
			zero.push_back(i);
	}

	if (one.size() == 1)
		return one[0];
	if(one.size()==0&&zero.size()==1)
		return zero[0];
	return -2;
}

 

 

 

 

in:
0 1
1 1
0 2
0 3
0 4
0 5
0 6
0 7
0 7
out:-1

 

이건 내가 틀렸을 때 유용했던 반례이다. 

 

 

문제가 참 생각할게 많고 귀찮다~~혹시나 답답해서 풀이를 찾고 있는 분께 도움이 되었기를..

 

'PS' 카테고리의 다른 글

프로그래머스 배달  (0) 2019.10.25
백준 17090 미로 탈출하기  (0) 2019.08.14
백준 13251 조약돌 꺼내기  (0) 2019.08.13

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

 

17090번: 미로 탈출하기

크기가 N×M인 미로가 있고, 미로는 크기가 1×1인 칸으로 나누어져 있다. 미로의 각 칸에는 문자가 하나 적혀있는데, 적혀있는 문자에 따라서 다른 칸으로 이동할 수 있다. 어떤 칸(r, c)에 적힌 문자가 U인 경우에는 (r-1, c)로 이동해야 한다. R인 경우에는 (r, c+1)로 이동해야 한다. D인 경우에는 (r+1, c)로 이동해야 한다. L인 경우에는 (r, c-1)로 이동해야 한다. 미로에서 탈출 가능한 칸의 수를 계산해보자. 탈출 가능한

www.acmicpc.net

 

[주의할 조건]

(r , c)에 적힌 문자에 따라서, 가야하는 방향이 정해져 있다.

 

[구하는 값]

그 칸에서 이동을 시작했을 때, 미로의 경계 밖으로 이동하게 되는 칸이 총 몇 칸인지 구한다.

 

[생각의 흐름]

 

1. 모든 '그 칸'에 대해서 시뮬레이션을 해보고, 결과에 따라서 정답의 카운트를 올려준다. 

현재 위치한 칸에 어떤 문자가 적혀있는지에 따라서 갈 방향이 정해져있기 때문에

내가 따로 특별한 조건이나 로직을 설정할 필요가 없다. 

즉, 요소에 움직일 수 있는 동력(?)을 불어넣고 알아서 갈 수 있는 방향으로 움직이다가

경계 밖으로 나온 것만 카운트 해준다..'칸마다 경로가 유일하므로' 큐에 넣었다가 큐에서 요소가 빠질

때 카운트가 곧 정답이다.

 

~~라고 생각하면 함정이 보인다. 요소가 큐에서 언제 빠지는가? 항상 빠질 수 있는가? 만약 맵이

RD

UL

이렇게 생겼을 때 R에서 출발하면 빙글빙글 무한루프에 빠진다. 그렇다고 visit을 체크해버리면

RDU
URU

DRL

이런 맵에서 (1,3)인 U가 먼저 큐에 들어갔다가 나와서 visit 체크를 해뒀는데 

(2,3)인 U에서 올라가지 못하게 된다.  

 

2.생각을 다르게 해봐야한다!

경로가 하나 있으면 그 안의 요소들은 한 경로에 포함이 된다는 것에 주목하자.(이 문제를 해결해야한다)

그렇다면 그 경로를 대표할만한 요소는??경로의 끝, 경계 부근에 있는 요소일 것이다.

맵의 어떤 요소이든 가장자리를 거쳐서 경계 밖으로 나갈 것이다.

그 말은 즉, 가장자리에서 출발하되 역으로 이동을 하면 해당 요소들을 모두 만날 수 있다는 뜻이다.

가장자리에 있는 요소들 중에서 바로 경계를 벗어날 수 있는 요소들만

큐에 넣고, 역방향으로 이동하며 만나는 요소들을 모두 카운트 해준다. 

 

물론 역방향으로 이동할 때엔 네방향을 모두 봐야한다.

이전 칸에서 지금의 칸으로 올 때 왼쪽으로 꺾어서 왔는지 내려왔는지 올라왔는지 모르기 때문이다. 

그렇게해서 만난 요소는 나가는 것이 가능한 칸이라고 결정이 난 것이고, 그 요소를 다시 확인할 필요는 없다.

그래서 visit으로 체크를 해준다. 

마치 동굴의 입구를 찾아서, 길을 뚫어내며 갇혀있던 사람들을 구하는 것과 같다. 이미 만난 사람은

내가 뚫어온 길로 가면 되니 해결된 사람이고, 나는어디 처박혀 있는 친구들을 더 찾으러 깊이 들어가는 셈이다.

(예전에 탈옥 문제가 생각난다..ㅎㅎ)

 

+사실, 무한루프에 빠지는 경우가 생각나지 않더라도 500x500의 맵에서 최악의 경우를 가정했을때

(1,1)에서 지그재그로 모든 요소를 거쳐서 (500,500)으로 빠져나가는 경우를 생각하면

그 사이의 요소들이 같은 경로를 통해 (500,500)으로 다시 빠져나가하므로

시간복잡도가 n^2이 되는 것을 알 수 있다. (요소가 총 25만개이므로 시간초과가 날 시간복잡도이다.)

그냥 대충 큐에 때려넣어버리면 안되는 문제!!

 

 

 

 

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<queue>
using namespace std;
struct pos {
	int x, y;
};
int offset[4][2] = { { 0,1 },{ 1,0 },{ 0,-1 },{ -1,0 } };  //R D L U
int map[501][501];
int visit[501][501];
int main() {
	//freopen("Text.txt", "r", stdin);
	int n, m, cnt = 0;
	
	queue<pos> q;
	cin >> n >> m;
	for (int i = 0; i < n; i++)
		for (int j = 0; j < m; j++) {
			char ch;
			cin >> ch;
			if (ch == 'R')   //입력받을때 문자 대신 숫자로 입력
				map[i][j] = 0;
			else if (ch == 'D')
				map[i][j] = 1;
			else if (ch == 'L')
				map[i][j] = 2;
			else
				map[i][j] = 3;

			int x = i + offset[map[i][j]][0];  //바로 다음 칸이 경계를 넘는지 확인
			int y = j + offset[map[i][j]][1];
			if (x < 0 || y < 0 || x >= n || y >= m) {
				pos p;
				p.x = i;
				p.y = j;
				visit[i][j] = 1;
				q.push(p);  //뚫린 가장자리라면 큐에 넣음
				cnt++;
			}
		}

	while (!q.empty()) {   
		pos cur = q.front();
		q.pop();
		for (int d = 0; d < 4; d++) {
			int x = cur.x + offset[d][0];
			int y = cur.y + offset[d][1];
			if (x < 0 || y < 0 || x >= n || y >= m || visit[x][y] == 1)
				continue;
			if (map[x][y] == (d + 2) % 4) {  //역방향으로 나간다
				visit[x][y] = 1;
				pos pn;
				pn.x = x;
				pn.y = y;
				q.push(pn);
				cnt++;
			}
		}
	}

	cout << cnt;
	return 0;
}

 

 

 

 

 

 

'PS' 카테고리의 다른 글

프로그래머스 배달  (0) 2019.10.25
백준 17349 1루수가 누구야  (0) 2019.08.14
백준 13251 조약돌 꺼내기  (0) 2019.08.13

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

 

13251번: 조약돌 꺼내기

첫째 줄에 뽑은 조약돌이 모두 같은 색일 확률을 출력한다. 정답과의 절대/상대 오차는 10-9까지 허용한다.

www.acmicpc.net

 

[주의할 조건]

색상의 종류: M개 (1~50)

조약돌 총 갯수: N개 (1~50*50)....각 색상의 조약돌 개수는 1<=x<=50 이므로 

뽑는 돌 갯수: K개 (1~50*50)

출력값의 오차는 10^-9까지 허용....소수점 이하 10자리까지 구하면 오차 내의 범위로 구해진다.

깔끔하게 %.15lf로 구하면 됨!

 

[구하는 값]

뽑은 조약돌이 모두 같은 색일 확률

 

[생각의 흐름]

1. 처음에는  분자: 전체 조약돌에서 뽑는 경우의 수 , 분모: a컬러로 모두 뽑는 수+ b컬러로 뽑는 수....

로 둬서 분모/분자 = (aCk + bCk + cCk ...) /(nCk) 를 계산하려고 했다. <-a: 색상이 a인 돌의 갯수

 

하지만!!그렇게하면 DP로 풀어도 계속 오답이 뜬다..

조합

이 문제를 풀어보면 알겠지만, 

100C50 = 100891344545564193334812497256

정도되는 아주아주 큰 수이면 long long의 범위조차 벗어나기 때문에 

string을 사용해서 big integer를 구현해야한다. 한마디로 십진수 덧셈을 구현해야 한다는 말...ㅎㅎ

조약돌 구하기 문제에선 2500C50도 구할 수 있어야하기 때문에

처음 생각한 방법으로 풀면 오답이 나올 수 밖에 없다. 그렇다고 이 문제에서 big integer를 구현하기엔...

너무 귀찮다ㅠㅠ

 

2.그래서 아예 확률을 구하는 식 자체를 다르게 잡아야한다. (이 문제 분류가 '큰 수'가 아닌 '확률'인 이유다.)

경우의 수를 다 구해서 확률을 계산하지 말고, 큰 수가 나오기 전에 미리미리 확률로 만들어두면 된다.

즉,  "각 컬러에 대해 뽑는 모든 경우의 수/ 모든 경우의 수"를 계산하는 것이 아니라

"a컬러에 대해 뽑는 확률"+"b컬러에 대해 뽑는 확률" + ....을 구해야하는 것이다.

 

 

n=200 , m= 50, k=3 에서 a컬러의 돌갯수가 40이라고 가장하자. 

 

a컬러로만 모든 돌을 뽑는 확률

= (40/200)*(39/199)*(38/198)= (40*39*38)/ (200*199*198).

 

처음부터 조합식으로 바로 풀려고 하면 쉽게 오답이 뜰 수 있는 문제이다. 

이런 식으로 각 컬러별로 확률을 구해서 모두 더해준 뒤, 소수점 이하 15자리까지 출력을 하면 통과할 수 있다. 

 

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
int color[50];
double p[50];
int main() {  
	int n = 0, m, k;
	scanf("%d", &m);
	for (int i = 0; i < m; i++) {
		scanf("%d",&color[i]);
		n += color[i];
	}
	scanf("%d",&k);

	for (int i = 0; i < m; i++) {
		if (color[i] < k)  //!!!!이 경우는 넘어가야함. 2개중에서 4개 못고르니..
			continue;
		double tmp = 1.0;
		for (int j = 0; j < k; j++) {  //k를 빼서 0이되는 경우 있을수도 있으니까..
			tmp *= (double)(color[i] - j) / (n - j);
		}
		p[i] = tmp;
	}
	double ans = 0;
	for (int i = 0; i <m; i++) {
		ans += p[i];
	}

	printf("%.15lf",ans);  
	return 0;
}

'PS' 카테고리의 다른 글

프로그래머스 배달  (0) 2019.10.25
백준 17349 1루수가 누구야  (0) 2019.08.14
백준 17090 미로 탈출하기  (0) 2019.08.14

+ Recent posts