이 문제의 조건은 두 가지이다.
① 이웃한 집과의 색은 달라야 한다.
② 첫 번째 집과 마지막 집의 색은 달라야 한다.

 

예시를 이용해 살펴보기


첫 번째 조건은 전형적인 DP로 해결할 수 있지만, 두 번째 조건은 조금 다른 방식으로 체크해줘야 한다.

문제의 예시에서

① 번 조건만 생각해 봤을 때에는,

D[i][j] : i번째 집을 j색으로 칠한다고 했을 때 비용의 최솟값,
a[i][j] : i번째 집을 j색으로 칠했을 때의 비용일 때

점화식은 D[i][j]=min(D[i-1][(j+1)%3], D[i-1][(j+2)%3]) + a[i][j] 이다.
여기에서 (j+1)%3, (j+2)%3의 표현은 j와는 다른 색을 선택한다는 의미이다.

즉, i번째에 j=2(파랑)을 선택했을 때, i-1번째에는 0(빨강), 1(초록)중에 하나 를 선택한다는 뜻이다.

이렇게 구한 D[n][0], D[n][1], D[n][2]값들의 최솟값이 답이 된다.

즉, n번째에 칠한 색(j)에 따른 비용의 최솟값(D[n][j])들을 비교하여 그중 가장 작은 비용이 답이 된다.

위의 예시에서는 D[n][0]=96, D[n][1]=172, D[n][2]=185가 되어,

D[n][0]이 답이 된다. 즉, 마지막 n번째 집에 빨간색을 칠했을 경우의 비용의 최솟값이 다른 색을 칠했을 경우와 비교해 가장 작다는 뜻이다.

 

문제풀이의 Key


여기에서 ②번의 조건을 추가한다면, 첫 번째 선택한 색과 마지막의 선택한 색을 비교해서 같으면 선택하지 못하게 해야 한다.

우리가 구하는 정답은 비용의 최솟값이다.

첫 번째 색이 k라고 하고, D [n][k](n번째 집에 k색을 칠할 때 비용의 최솟값)에 매우 큰 값을 넣는다면

마지막에 저절로 답에서 제외될 것이다.

위의 방법이 이 풀이의 핵심이다.

해결


첫 번째에 선택한 색을 k라고 두고, 

위에 서술해놓은 이 풀이의 핵심 key를 사용하여

첫 번째 집에 k가 아닌 색의 비용을 1000*1000이라 하고,

마지막 집에 k인 색의 비용을 1000*1000이라고 한다.

노란색은 선택받은 색, 빨간색은 선택받지 못하는 색이다.

위와 같은 방법으로 k=0~2까지 순회하면서 k값에 따른 문제의 최솟값을 구한 뒤,

모든 최솟값을 비교해 그중 가장 작은 비용을 선택하면 해결된다.

코드


#include <bits/stdc++.h>
#define MAX 1000

using namespace std;

int main()
{
  ios_base::sync_with_stdio(false);
  cin.tie(nullptr);

  int n;
  int a[MAX+1][3]={0,}, d[MAX+1][3]={0,};
  cin>>n;
  for(int i=1; i<=n; i++){
    for(int j=0; j<3; j++){
      cin>>a[i][j];
    }
  }
  int res=1000*1000;
  for(int k=0; k<3; k++){
    // 첫번째집을 k로 선택하는 경우
    //cout<<k<<'\n';
    for(int i=1; i<=n; i++){
      for(int j=0; j<3; j++){
        d[i][j]=min(d[i-1][(j+1)%3],d[i-1][(j+2)%3])+a[i][j];
        if(i==1){
          d[i][j] = 1000*1000;
          if(j==k) d[i][j]=a[i][j];
        }else if(i==n && j==k){
          d[i][j] = 1000*1000;
        }
        //cout<<d[i][j]<<' ';
      }
      //cout<<'\n';
    }
    for(int j=0; j<3; j++){
      res=min(res, d[n][j]);
    }
  }
  cout<<res;
  return 0;
}

'프로그래밍 > PS' 카테고리의 다른 글

[백준 12904] A와 B  (0) 2021.05.28
[백준 2109] 순회강연  (0) 2021.05.26
[백준 14500] 테트로미노  (0) 2021.03.25
[백준] 골드V 달성  (0) 2021.03.24
[백준 2133번] 타일채우기  (0) 2021.03.18

먼저 타일을 채울 수 있는 단위타일을 만들어 보면 아래와 같다

2*3의 타일은 3개지만, 2n * 3의 타일들은 2개씩 나온다. (1<n)


일단 4*3의 타일을 채우는 경우를 생각해보자

위와 같이 2*3단위타일이 2개 오거나, 4*3단위 타일이 1개 올 수 있다.

D[i]를 가로의 길이가 i인타일을 채우는 경우의 수라고 하면,

2*3단위 타일이 2개인 경우는 다르게 표현하면, D[2]에 2*3단위 타일이 연속해서 오는 경우이다 (곱사건)

4*3타일이 1개인 경우는 다르게 표현하면, 가로의 길이가 0인 타일을 채운 경우인 D[0]에 4*3단위 타일이 연속해서 오는 경우이다 (곱사건)

2*3단위 타일을 만드는 경우의 수는 3가지, 4*3단위 타일을 만드는 경우는 2가지이므로

 D[4] = D[2]*3 + D[0]*2

단, 여기에서 D[0]은 0*3의 타일에 아무것도 채우지 않는 경우의 수 이므로 D[0]=1이다.


조금 더 확장해서 6*3의 타일을 만드는 경우를 생각해보자.

이번에는 위의 예시에서 학습한 내용을 바로 적용해보았다.

2*3단위 타일을 만드는 경우의 수는 3가지, 2n * 3단위 타일을 만드는 경우는 2가지 (단, 1<n)이므로,

D[6] = D[4]*3 + D[2]*2 + D[0]*2


위의 예시들을 바탕으로 점화식을 세워보면

D[n] = D[n-2] * 3 + ∑D[n-2k] * 2 ( 단, 2<2k<=n, n은 2<n인 짝수 ), D[2]=3, D[0]=1

D[n] = 0 ( n은 홀수 )


#include <bits/stdc++.h>
#define MAX 30

using namespace std;

int main()
{
  ios_base::sync_with_stdio(false);
  cin.tie(nullptr);

  int n;
  cin>>n;
  int d[MAX+1]={0,};
  d[0]=1;
  for(int i=1; i<=n; i++){
    if(i%2==0){
      if(i>=2) d[i]+=d[i-2]*3;
      if(i>=4){
        for(int j=4; j<=i; j+=2){
          d[i]+=d[i-j]*2; 
        }
      }
    }
  }
  cout<<d[n];
}

 

'프로그래밍 > PS' 카테고리의 다른 글

[백준 12904] A와 B  (0) 2021.05.28
[백준 2109] 순회강연  (0) 2021.05.26
[백준 14500] 테트로미노  (0) 2021.03.25
[백준] 골드V 달성  (0) 2021.03.24
[백준 17404번] RGB거리 2  (0) 2021.03.19

상황


서버에는 A의 ip로만 접속할 수 있도록 방화벽이 설정되어있습니다.

그렇기에 다른 곳에서 B의 ip로 서버에 접속할 수 없는 상태입니다.

구성


위와 같이 방화벽 예외 설정이 되어있는 A의 ip를 경유 서버로 활용하여 목표하는 서버에 접속하는 방법을 사용합니다

이것을 Jump Host라고 합니다.

준비


B는 mac(linux or unix), A는 window 인 본인의 환경으로 설명을 하겠습니다.

B가 window일경우 putty를 , A가 mac일 경우 macOS SSH서버 를 준비하면 됩니다.

1. A가 클라이언트가 아닌 서버가 되어야 합니다.

2. A는 포트포워딩이 되어 있어야 합니다. 

3. 접속이 원활한지 확인해봅시다.

 

1. A를 경유 서버로 만들기

더보기

1. A에 OpenSSH서버 설치

검색창에 앱 및 기능을 검색합니다

선택적 기능에 들어갑니다

기능 추가를 클릭합니다

ssh를 검색한 후, OpenSSH 서버를 설치합니다

설치 완료


2. 서버 실행

관리자 권한으로 PowerShell을 실행해 줍니다.

아래의 명령어를 입력해서 서버를 실행, 종료할 수 있습니다.

start-service sshd stop-service sshd

3. 비대칭 키 인증 적용을 위한 서버 설정

비밀번호를 이용하면 보안에 취약할 수 있으므로 Public Key를 이용한 인증을 사용합니다.

관리자 권한으로 실행한 PowerShell에 아래와 같이 입력합니다.

notepad.exe $env:Programdata\ssh\sshd_config

이 파일에서 Ctrl+F를 이용해 아래의 항목들의 주석을 해제하고 값을 변경해줍니다.

PubkeyAuthentication yes
PasswordAuthentication no
PermitEmptyPasswords no

그리고 아래 코드 블록을 찾아서 주석 처리해줍니다

Match Group administrators
	AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys
주석처리한 모습

4. 클라이언트(B)에서의 Public Key 발급

이제 위에 설정한 서버(A)의 클라이언트(B)가 될 pc에서 Public Key를 발급받겠습니다.

여기서의 B가 이번 절의 대상(클라이언트)입니다.

본인의 경우 클라이언(B)트 pc는 맥북입니다.

일단, 이미 발급한 Key가 있는지 확인하기 위해 터미널을 열어 아래의 명령를 입력합니다

cat ~/.ssh/id_rsa.pub
이렇게 나온다면 이미 존재하는 것 이므로 다음 절로 넘어갑니다.

Key를 발급하는 것은 매우 쉽습니다. 아래와 같은 명령만 입력하면 됩니다.

ssh-keygen

아래 질문에는 Enter를 눌러 Default값으로 설정해 줍니다.

default값인 ~/.ssh/id_rsa

그리고 key를 사용할 때 입력할 비밀번호를 입력해줍니다. 그냥 Enter을 치면 비밀번호 없이 접속 가능합니다.

설정이 끝난 상태

이렇게 발급이 끝났으면 다시 한번 아래 명령을 입력해 key값을 확인합니다.

cat ~/.ssh/id_rsa.pub
key값을 확인

 


5. 클라이언트(B)의 key값을 서버에 저장

다시 서버(A)로 사용할 윈도우 컴퓨터, 위에서 봤을 때 A컴퓨터로 돌아옵니다.

관리자 권한으로 PowerShell을 켠 뒤, 아래 명령어를 입력해줍니다.

mkdir "$HOME\.ssh"

이제 클라이언트(B)의 key값을 저장할 파일을 만들겠습니다. 아래 명령어를 순서대로 입력해줍니다.

$authorizedKeyFilePath = "$HOME\.ssh\authorized_keys"
New-Item $authorizedKeyFilePath
notepad.exe $authorizedKeyFilePath

그런 다음, 클라이언트(B)에서 발급받은 key값을 모두 복사하여 이곳에 붙여 넣어줍니다.

ssh-rsa부터 전부 입력되어야 합니다.

이렇게 한 뒤 저장하면 서버(A)에 내 Public Key값이 저장되게 됩니다.


6. 인증 키를 저장한 파일의 권한 설정하기

똑같이 관리자 권한으로 실행한 PowerShell에 아래 명령을 순서대로 입력하면 됩니다.

icacls.exe $authorizedKeyFilePath /remove "NT AUTHORITY\Authenticated Users"
icacls.exe $authorizedKeyFilePath /inheritance:r
Get-Acl "$env:ProgramData\ssh\ssh_host_dsa_key" | Set-Acl $authorizedKeyFilePath
명령을 순서대로 입력한 모습

이렇게 하면 경유 서버로 사용할 서버(A)의 설정이 모두 완료됩니다.


2. 경유서버(A) 포트포워딩 하기


이부분은 공유기마다 서로 다르기 때문에 공유기에 따른 포트포워딩 방법을 검색하셔서 참고하시면 됩니다.

포트포워딩은 22번포트, 공유서버(A)의 IPv4주소로 포트포워딩 해주십시오.


3 .클라이언트(B)에서 서버(A)에 접속해보기


더보기

1. 서버의 정보 알아내기(내부 IP, 이름, 외부 IP)

일단 접속할 서버(A)의 IP와, UserName을 알아야 합니다.

그래서 서버(A)의 컴퓨터에서 다시 관리자 권한으로 PowerShell을 실행하여 아래 명령을 입력한다.

ipconfig

결과창에서 IPv4 주소를 참고하면 된다.

IPv4주소를 알아낸다.

아래 명령어로 서버를 켜고

start-service sshd

아래 명령어로 자기 자신의 서버(A)에 접속합니다.

ssh 172.xxx.x.xx(자기의 IPv4 주소)

질문이 나오면 yes(Enter)를 입력합니다.

우리가 필요한 것은 두번째 빨간 박스이다.

우리는 이전에 Public Key인증을 등록해놨기 때문에 Key가 없으면 접속이 허용되지 않습니다.

그래서 위와 같이 Permission denied오류가 나오는 것입니다.

위의 결과창에서 우리는 UserName을 얻을 수 있습니다.

본인의 경우는 빨간 박스 쳐진 sever입니다.

마지막으로 외부 IP를 알아내야 합니다.

간단하게 www.ipconfig.co.kr 에 접속하여 나오는 주소가 외부 IP입니다.


2. 클라이언트(B)에서 서버(A)에 접속하기

이제 클라이언트(B)로 넘어가 봅시다.

외부에서 서버(A)에 접속할 때에는 IPv4 주소가 아닌 서버의 외부IP 주소를 통해 들어가게 됩니다.

외부IP주소를 통해 들어가면 포트포워딩이 된 내부IP로 연결이 되게 되는것입니다.

terminal창에 아래와 같이 아까 알아낸 Username@외부IP 주소를 입력합니다.

ssh sever@112.xx.x.xx (UserName@외부IP 주소)

질문이 나오면 yes(Enter)을 입력해줍니다.

그런 뒤 아까 Key를 만들면서 입력했던 비밀번호를 입력해준다. 비밀번호 없이 그냥 Enter 했다면 이 창은 나오지 않습니다.

아래와 같이 바뀌면 접속이 성공적으로 완료했다는 것을 알 수 있습니다.

접속 완료! 맨위의 Title도 바뀌었다.

실행


이제 클라이언트(B)->경유 서버(A)->서버의 방식으로 접속할 준비가 모두 끝났습니다.

이번에 사용할 방법은 ProxyCommand인데

ssh의 프록시 옵션인 ProxyCommand와 ssh 내장 netcat proxy를 사용하는 방법입니다.

아래와 같은 포맷으로 입력해주면 됩니다.

ssh -o ProxyCommand="ssh -W %h:%p server1" server2

server1은 경유 서버(A)이고 server2는 목적 서버.

%h는 server1, %p는 server2의 ssh포트(default=22)입니다.

만약목적 서버로 접속하기 위한 포트가 따로 있다면 %p대신 포트번호를 입력해 주면 됩니다.

본인의 경우 동아리에서 NIPA로부터 지원받은 GPU 서버를 사용하고, 접속 포트가 정해져 있기 때문에 그것을 사용합니다.

포맷에 맞게 입력했다면, 비밀번호는 이전에 발급받은 key의 비밀번호를 입력하면 됩니다.

fingerprint에 대한 질문이 나오면 역시 yes(Enter)를 입력해줍니다.

마지막으로 목적 서버의 비밀번호를 입력해줍니다.

접속완료!


마무리


이렇게 한번 설정이 완료됐다면, 간단하게 경유 서버를 이용해 접속할 수 있습니다.

zsh를 사용하는 경우 ~/.zshrc,

bash를 사용하는 경우 ~/.bash_profile 파일을 열어,

내부에 아래 명령어를 넣어줍니다.

alias nipa="ssh -o ProxyCommand="ssh -W %h:%p sever@112.xx.x.xx" ubuntu@175.xx.xx.xx"

이후에는 아래와 같이 nipa라는 명령어 하나로 쉽게 접속할 수 있습니다.

 

이 기능을 자주 사용하신다면, PowerShell을 관리자권한으로 실행한 후 아래 명령을 입력하십시오.

Set-Service -Name sshd -StartupType 'Automatic'

경유서버(A)의 pc를 켤때마다, 서버를 실행할 수 있도록 도와줍니다.

sshd라는 기능이 백그라운드 자원을 메모리기준 4mb정도밖에 사용하지 않는것을 확인하였습니다.

그래서 자동실행을 해도 메모리 걱정 없이 사용할 수 있을 것입니다.

메모리 사용량 4mb정도


출처

더보기

선언과 동시에 초기화 ( 0 만 가능 )

오늘 백준 11053번 가장 긴 증가하는 부분수열4를 해결할 때, 배열 초기화를 잘못하는 실수를 했다.

앞서 l[1001]배열을 {-1,-1,-1,...,-1}로 초기화 하려고 시도했다.

int a[1001], d[1001], l[1001]={-1,}

하지만 디버깅을 하며 살펴보니 왼쪽과 같이 {-1, 0, 0, ... 0} 으로 초기화가 되어있었다.

왜그런가 찾아보니, c++에서 {n, } 형태로 배열을 초기화 할 때는 n==0인 경우에만 가능하다는 것이였다.


추가) 2차원배열도 똑같은 방법으로 초기화 할 수 있다.

int d[201][201]={0,}

 

Memset함수 ( 0, -1 만 가능)

-1로 초기화 하기 위해서는

memset(l,-1,sizeof(l));

이렇게 memset을 활용해서 초기화 해야하고, memset함수도 주의해야 할 점이 있는데

초기화할 수 있는 값은 0과 -1만 가능하다는 것이다.

For문 ( 모든 경우 )

그 외의 숫자로 초기화 하기 위해서는 마음 편하게 for문으로 초기화 하는게 나을 것 같다.

for(int i=0; i<n; i++){
	l[i]=3;
}

 

비교함수 내부

반복문 내부분석 및 풀이

이 문제를 해결하기 위한 키는 src1의 인덱스의 범위가 char자료형의 범위라는것을 파악하는것이라고 생각한다.

관련링크

더보기

문제주소

 

rev-basic-6

Reversing Basic Challenge #6 이 문제는 사용자에게 문자열 입력을 받아 정해진 방법으로 입력값을 검증하여 correct 또는 wrong을 출력하는 프로그램이 주어집니다. 해당 바이너리를 분석하여 correct를 출

dreamhack.io

2021.03.10 - [프로그래밍/리버싱] - [Wargame] dreamhack_rev-basic-5 풀이

 

[Wargame] dreamhack_rev-basic-5 풀이

비교함수 내부 반복문 내부 이 문제의 핵심은 한 단어를 알아내는 것이다. 그렇게 하지 못하면 브루트포스를 사용하는 방법밖에 없다. 그 핵심의 한 단어를 알아내는 방법을 아래에 설명해놓았

self-developing-developer.tistory.com

2021.03.09 - [프로그래밍/리버싱] - [Wargame] dreamhack_rev-basic-4 풀이

 

[Wargame] dreamhack_rev-basic-4 풀이

비교함수 내부 풀이 이번 문제도 3번문제와 마찬가지로 12번라인에서 src가 있는 메모리주소를 알려주었고, 우리가 입력한 str값을 알맞게 변형하여 src값과 일치시키는 문제이다. sar 연산자는 arit

self-developing-developer.tistory.com

2021.03.08 - [프로그래밍/리버싱] - [Wargame] dreamhack_rev-basic-3 풀이

 

[Wargame] dreamhack_rev-basic-3 풀이

비교함수 내부 실질적인 비교를 하는 반복문 내부를 살펴보면 이 전에도 rev-basic-0,1,2를 풀었지만 풀이를 쓸 난이도는 아니였기 때문에 이번 문제부터 풀이를 작성하였다. 이렇게 리버싱 공부 2

self-developing-developer.tistory.com

비교함수 내부

반복문 내부

이 문제의 핵심은 한 단어를 알아내는 것이다.

그렇게 하지 못하면 브루트포스를 사용하는 방법밖에 없다.

그 핵심의 한 단어를 알아내는 방법을

아래에 설명해놓았다.

반복문이 어떻게 돌아가는지 직접 손으로 작성해본 결과

핵심적인 열쇠를 알아낼 수 있었다.

 

관련 링크

더보기

 

비교함수 내부

실질적인 비교를 하는 반복문 내부를 살펴보면

이 전에도 rev-basic-0,1,2를 풀었지만 풀이를 쓸 난이도는 아니였기 때문에 이번 문제부터 풀이를 작성하였다.

이렇게 리버싱 공부 2일차 문제풀이를 완료하였다.

리버싱 공부 시작(21.03.07)

+ Recent posts