문제

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

풀이

동전이 2개 있는데 하나만 table 밖으로 밀어내야 하는 문제.
하나만 밖으로 보내기 위해선

  • 두 동전이 겹치면 안됨(겹치면 둘 다 떨어지거나 둘 다 table위에 남거나 밖에 없기 때문에)

bfs로 모든 경우를 검사하고 위 예외만 처리하면 시간초과없이 해결

코드

#include <iostream>
#include <queue>

using namespace std;

int N, M;
char table[21][21];
struct qData {
  pair<int, int> coin1, coin2;
  int depth;
};

pair<int, int> c1, c2;
bool bC1= false;

bool isOutside(int x, int y) {
  if(0 > x || x >= M) {
    return true;
  }
  if(0 > y || y >= N) {
    return true;
  }
  return false;
}

int main(void) {
  cin >> N >> M;
  for(int y=0;y<N;++y) {
    for(int x=0;x<M;++x) {
      cin >> table[y][x];
      if(table[y][x] == 'o') {
        if(bC1) {
          c2 = make_pair(x, y);
        } else {
          c1 = make_pair(x, y);
          bC1 = true;
        }
      }
    }
  }

  queue<qData> q;
  qData initQData;
  initQData.coin1 = c1;
  initQData.coin2 = c2;
  initQData.depth = 0;
  q.push(initQData);
  int dx[4] = {1,-1,0,0};
  int dy[4] = {0,0,1,-1};
  while(!q.empty()) {
    pair<int, int> coin1 = q.front().coin1;
    pair<int, int> coin2 = q.front().coin2;
    int depth = q.front().depth;
    q.pop();
    for(int k=0;k<4;++k) {
      int nextC1X = coin1.first + dx[k];
      int nextC1Y = coin1.second+ dy[k];
      int nextC2X = coin2.first + dx[k];
      int nextC2Y = coin2.second+ dy[k];
      int outsideCnt = 0;
      if(isOutside(nextC1X, nextC1Y)) {
        outsideCnt++;
      }
      if(isOutside(nextC2X, nextC2Y)) {
        outsideCnt++;
      }
      if(outsideCnt == 1) {
        cout << depth+1;
        return 0;
      }
      if(outsideCnt == 0) {
        pair<int, int> nextC1 = make_pair(nextC1X, nextC1Y);
        pair<int, int> nextC2 = make_pair(nextC2X, nextC2Y);
        if(table[nextC1Y][nextC1X] == '#') {
          nextC1 = coin1;
        } 
        if(table[nextC2Y][nextC2X] == '#') {
          nextC2 = coin2;
        } 
        if(nextC1X == nextC2X && nextC1Y == nextC2Y) {
          continue;
        }
        if(depth != 9) {
          qData nextQData;
          nextQData.coin1 = nextC1;
          nextQData.coin2 = nextC2;
          nextQData.depth= depth+1;
          q.push(nextQData);
        }
      }
    }
  }
  cout << -1 << endl;
  return 0;
}

문제

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

풀이

조건대로 차례대로 구현하면 됐다.

  1. 컨베이어 벨트를 회전시킨다.
    1-1 N위치에 로봇이 있다면 로봇을 내린다.
  2. 로봇을 이동시킨다.
    2-1 N위치에 로봇이 있다면 로봇을 내린다.
    2-2 로봇이 이동할 때 내구도를 1 감소시킨다.
  3. 1위치에 로봇을 올린다.
    3-1 1위치에 로봇을 올릴 때 내구도를 1 감소시킨다.
  4. 종료조건을 확인한다.

코드

#include <iostream>

using namespace std;

struct can {
  bool onRobot;
  int left;
};

int N, K;
can A[1001];

void rotateA() {
  can temp = A[N * 2 - 1];
  for(int i=N * 2-1;i>=0;--i) {
    A[i] = A[i-1];
  } 
  A[0] = temp;
  if(A[N-1].onRobot) {
    A[N-1].onRobot = false;
  }
}

void move() {
  for(int i=N - 2;i>=0;--i) {
    if(A[i].onRobot) {
      int nextIdx = i + 1;
      if(!A[nextIdx].onRobot && A[nextIdx].left != 0) {
        if(nextIdx == N-1) {
          A[i].onRobot = false;
          A[nextIdx].left--;
          continue;
        }
        A[i].onRobot = false;
        A[nextIdx].onRobot = true;
        A[nextIdx].left--;
      }
    }
  }
}

void pushRobot() {
  if(!A[0].onRobot && A[0].left != 0) {
    A[0].onRobot = true;
    A[0].left--;
  }
}

int cntEmptyCan() {
  int ret = 0;
  for(int i=0;i<N * 2;++i) {
    if(A[i].left == 0) {
      ret++;
    } 
  }
  return ret;
}

void printLeft() {
  cout << "PL" << endl;
  for(int i=0;i<N * 2;++i) {
    cout << A[i].left << ' ';
  }
  cout << endl;
  for(int i=0;i<N * 2;++i) {
    cout << A[i].onRobot<< ' ';
  }
  cout << endl;
}

int main(void) {
  cin >> N >> K;
  for(int i=0;i<N * 2;++i) {
    cin >> A[i].left;
    A[i].onRobot = false;
  }

  int ans = 1;
  while(true) {
    rotateA();
    move();
    pushRobot();
    if(cntEmptyCan() >= K) {
      cout << ans;
      return 0;
    } 
    ans++;
  }
  return 0;
}

문제

https://app.codility.com/programmers/lessons/15-caterpillar_method/min_abs_sum_of_two/

풀이

  1. 입력 배열을 정렬한다.
  2. 배열의 양 끝 값의 합부터 시작해서 점점 배열의 안쪽으로 들어가며 계산한다.
    • 양 끝 값의 합을 구하고 둘 중 절댓값이 큰 값의 Index를 한칸 안쪽으로(시작 index는 +1, 끝 index는 -1) 보낸다.
  3. 두 index가 같아졌을 때 계산을 종료하고 이 때의 index가 가리키는 값이 절대값이 가장 작은 원소이므로 이 값의 2배의 절대값도 정답의 후보다.

문제 이해

삼각형 안에 숫자가 있고 만들 수 있는 부분 삼각형의 숫자 총합 중 최대값을 구하는 문제다. 숫자 범위가 정수이기 때문에 한 값이 튀게 되면 결과를 예상할 수 없어 모든 경우를 다 구해봐야한다.

평범한 dp로는 문제를 풀 수 없다. 메모이제이션 해야하는 양이

row : 400, col : 400 * 2 + 1, 부분삼각형 경우의수 : 400 으로 메모리 제한을 초과한다.

int memo[400][801][400] // 메모리 제한 초과 

모든 경우의 수를 다 구한다면 시간 복잡도는

r * c * 경우의수 * 경우의 수 뽑아 더하는 것 = 400 * 800 * 400 * 약 400 = 51,200,000,000

으로 시간제한 1초를 초과한다.

경우의 수를 뽑아 더하는 것은 처음 입력받을 때 누적 합을 구해 둔 다음 O(1)만에 한 row의 합을 구할 수 있도록 하면 시간제한안에 들어온다.

풀이

Process

void process() {
  for(int r=N-1;r>=0;--r) {
    for(int c=0;c<2*r+1;c+=2) {
      cal(r,c);
      if(c != 2 * r) {
        reverseCal(r,c+1);
      }
    }
  } 
 }

모든 점을 돌면서 한번씩 해봐야한다.

column은 짝수 번째일 때 size 2이상의 피라미드형 부분 삼각형을 만들 수 있다.

홀수 번째일때는 size 2 이상의 역삼각형을 만들 수 있다.

input 과 부분합

void input() {
  ans = -1001;
  memset(cSum, 0, sizeof(cSum));
  memset(table, 0, sizeof(table));
  for(int r=0;r<N;++r) {
    for(int c=0;c<2*r+1;++c) {
      cin >> table[r][c];
      ans = max(table[r][c], ans);
      if(c != 0) 
        cSum[r][c] += cSum[r][c-1] + table[r][c];
      else
        cSum[r][c] = table[r][c];
    }
  }
}

값 받으면서 부분합을 같이 계산해주었다.

피라미드형 부분 삼각형 계산

void cal(int r, int c) {
  int pSum = 0;
  for(int s=1;s<=N-r+1;++s) {
    if(c == 0) 
      pSum += cSum[r + s - 1][c + 2 * (s - 1)];
    else 
      pSum += cSum[r + s - 1][c + 2 * (s - 1)] - cSum[r + s - 1][c - 1];
    ans = max(pSum, ans);
  }
}

size는 지금 구해야 하는 row에서 더해야 하는 길이이다. size1 일 때는 r, c(시작점) 에서 부분 삼각형의 size 가 1일 때 값을 구하고 ans에 max값으로 최신화한다. 다음 size를 증가시키면서 한 row씩 증가시키며 해당하는 부분의 합을 더한다.

한 row에 해당하는 영역을 구하는 점화식이 위와 같이 나와 그대로 사용하면 된다.

역 피라미드형 부분 삼각형 계산

void reverseCal(int r, int c) {
 int pSum = 0;
  for(int s=1;s<=N;++s) {
    if(c >= 2 * s - 1 && 2 * r - c >= s * 2 - 1) {
      if(c - 2 * s + 1 >= 0) {
        pSum += cSum[r - s + 1][c] - cSum[r - s + 1][c - 2 * s + 1];
      } else {
        pSum += cSum[r - s + 1][c];
      }
    } else {
      break;
    }
    ans = max(pSum, ans);
  }
}

역 피라미드형은 row를 감소시키며 구해나간다. 점화식은 위와 같다.

종료조건을 따로 추가했다.

역 피라미드형이 가능한 size를 정하는데 위와 같은 방식으로 생각했다. 지금 size로 역 피라미드가 만들어지려면 선택한 c의 왼쪽, 오른쪽에 역피라미드의 가장 넓은 부분의 개수만큼 있어야 한다.

테스트 케이스

4 1 1 -1 1 1 -1 1 -1 1 1 -1 1 -1 1 -1 1
5 0 0 0 0 0 99 99 99 0 0 0 0 99 0 0 0 0 0 0 0 0 0 0 0 0
3 6 -24 0 12 -10 12 40 -4 6
5
         0
       0 0 0
     299 0 0 0 9
   -9 0 0 0 9 -9999 9
 -9 28 -9 0 9 9 9 9 9
5
         0
       0 0 0
     9 9 9 9 9
   0 -9 9 9 9 -9 0
 0 0 0 -9 9 -9 0 0 0
6
          0
        0 0 0
      0 1000 1000 1000 0
    0 9 9 -2 9 9 0
  0 0 0 9 9 9 0 0 0
0 0 0 0 0 9 -9999 0 0 0 0
6
          0
        0 0 0
      0 0 0 0 0
    0 9 9 9 9 9 0
  0 0 0 9 9 9 0 0 0
0 0 0 0 0 9 -9999 0 0 0 0
6
          0
        0 0 0
      0 1000 1000 1000 0
    0 9 9 -2 9 9 0
  0 0 0 9 9 9 0 0 0
0 0 0 0 0 9 -9999 0 0 0 0
0

// answer
1. 4
2. 396
3. 54
4. 336
5. 54
6. 3061
7. 81
8. 3061

코드

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

int N;
int table[401][801];
int cSum[401][801];
int ans;
void input() {
  ans = -1001;
  memset(cSum, 0, sizeof(cSum));
  memset(table, 0, sizeof(table));
  for(int r=0;r<N;++r) {
    for(int c=0;c<2*r+1;++c) {
      cin >> table[r][c];
      ans = max(table[r][c], ans);
      if(c != 0) 
        cSum[r][c] += cSum[r][c-1] + table[r][c];
      else
        cSum[r][c] = table[r][c];
    }
  }
}

void cal(int r, int c) {
  int pSum = 0;
  for(int s=1;s<=N-r+1;++s) {
    if(c == 0) 
      pSum += cSum[r + s - 1][c + 2 * (s - 1)];
    else 
      pSum += cSum[r + s - 1][c + 2 * (s - 1)] - cSum[r + s - 1][c - 1];
    ans = max(pSum, ans);
  }
}

void reverseCal(int r, int c) {
 int pSum = 0;
  for(int s=1;s<=N;++s) {
    if(c >= 2 * s - 1 && 2 * r - c >= s * 2 - 1) {
      if(c - 2 * s + 1 >= 0) {
        pSum += cSum[r - s + 1][c] - cSum[r - s + 1][c - 2 * s + 1];
      } else {
        pSum += cSum[r - s + 1][c];
      }
    } else {
      break;
    }
    ans = max(pSum, ans);
  }
}

void process() {
  for(int r=N-1;r>=0;--r) {
    for(int c=0;c<2*r+1;c+=2) {
      cal(r,c);
      if(c != 2 * r) {
        reverseCal(r,c+1);
      }
    }
  } 
 }

void output() {
  static int cnt = 1;
  cout << cnt++ << ". " << ans << endl;
}

int main(void) {
  freopen("b4902.txt", "r", stdin);
  cin >> N;
  while(N != 0) {
    input();
    process();
    output();
    cin >> N;
  }
}

'알고리즘' 카테고리의 다른 글

백준 20055 C++ 컨베이어 벨트 위의 로봇  (0) 2020.12.31
Codility - MinAbsSumOfTwo  (0) 2020.12.30
백준 2916 자와 각도기 c++  (0) 2020.09.15
백준 2210 숫자판 점프 c++  (0) 2020.09.15
백준 2933 미네랄 C++ 풀이  (0) 2020.09.12

풀이

  1. 만들 수 있는 각도로 새로운 각도를 만들어 set에 넣는다.
  2. set에 있는 원소를 vector에 넣은 다음 2중 for문을 통해 만들 수 있는 모든 각도를 만든 후 다시 set에 넣는다.
  3. 반복하다가 set의 크기와 vector의 크기가 같아지면 더 이상 추가될 게 없다는 것이므로 이 때 set에 현우가 외친 각이 있으면 YES, 없으면 NO

코드

#include <iostream>
#include <set>
#include <vector>

using namespace std;

int n, k;
vector<int> angle;
vector<int> output;
void input() {
  cin >> n >> k;
  int temp;
  for(int i=0;i<n;++i) {
    cin >> temp;
    angle.push_back(temp);
  }
  for(int i=0;i<k;++i) {
    cin >> temp;
    output.push_back(temp);
  }
}

int convert(int num) {
  if(num >= 360) {
    return num - 360;
  }
  if(num < 0) {
    return num + 360;
  }
  return num;
}

set<int> ans;
void process() {
  int nowAnsLength = 0; 
  for(int i=0;i<angle.size(); ++i) {
    ans.insert(convert(angle[i]));
  }
  for(int i=0;i<angle.size(); ++i) {
    for(int j=0;j<angle.size(); ++j) {
      ans.insert(convert(angle[i] + angle[j]));
      ans.insert(convert(angle[i] - angle[j]));
    }
  }
  while(nowAnsLength != ans.size()) {
    nowAnsLength = ans.size();
    vector<int> temp_vec;
    for(auto x : ans) {
      temp_vec.push_back(x);
    }
    for(int i=0;i<temp_vec.size(); ++i) {
      for(int j=0;j<temp_vec.size(); ++j) {
        ans.insert(convert(temp_vec[i] + temp_vec[j]));
        ans.insert(convert(temp_vec[i] - temp_vec[j]));
      }
    }
  }
  for(int i=0;i<output.size();++i) {
    if(ans.count(output[i]) == 0) {
      cout << "NO" << endl;
    } else {
      cout << "YES" << endl;
    }
  }
  return;
}

int main(void) {
  input();
  process();
}

'알고리즘' 카테고리의 다른 글

Codility - MinAbsSumOfTwo  (0) 2020.12.30
백준 4902 삼각형의 값 c++  (0) 2020.09.17
백준 2210 숫자판 점프 c++  (0) 2020.09.15
백준 2933 미네랄 C++ 풀이  (0) 2020.09.12
백준 2776 CPP, js  (0) 2020.09.04

풀이

모든 경우의 수를 다 해보면 되는 문제.

코드

#include <iostream>
#include <set>
#include <vector>

using namespace std;

char table[5][5];
set<string> s;

void input() {
  for(int i=0;i<5;++i) {
    for(int j=0;j<5;++j) {
      cin >> table[i][j];
    }
  }
}

bool rangeCheck(int nx, int ny) {
  return 0 <= nx && nx < 5 && 0 <= ny && ny < 5;
}

vector<char> temp;
int dx[] = {0,0,1,-1};
int dy[] = {1,-1,0,0};
void dfs(int y, int x) {
  if(temp.size() == 6) {
    string make = "";
    for(int i=0;i<temp.size(); ++i) {
      make += temp[i];
    }
    s.insert(make);
    return;
  }

  for(int k=0;k<4;++k) {
    int nx = x + dx[k];
    int ny = y + dy[k];
    if(rangeCheck(nx, ny)) {
      temp.push_back(table[ny][nx]);
      dfs(ny, nx);
      temp.pop_back();
    }
  }
}

void processing() {
  for(int i=0;i<5;++i) {
    for(int j=0;j<5;++j) {
      temp.push_back(table[i][j]);
      dfs(i, j);
      temp.pop_back();
    }
  }

  cout << s.size();
}

int main() {
  input();
  processing();
}

'알고리즘' 카테고리의 다른 글

백준 4902 삼각형의 값 c++  (0) 2020.09.17
백준 2916 자와 각도기 c++  (0) 2020.09.15
백준 2933 미네랄 C++ 풀이  (0) 2020.09.12
백준 2776 CPP, js  (0) 2020.09.04
[javascript] b1072.js  (0) 2020.09.03

개요

user 로그인 상태를 관리하기 위해 안전한 방법으로 Server에서 Client에게 쿠키로 sessionID를 발급해주고 이 쿠키를 통해 Server에 접속하면 sessionID값을 활용해 어떤 Client인지 식별하고 관련 정보를 제공하는 방법을 사용한다. 관련 기능을 쉽게 사용할 수 있게 해주는 express-session 모듈을 사용해 보자.

관련 그림

1. express-session 등록

const session = require('express-session');

app.use(session({
 secret: 'mySecretKey!@#$',
 resave: false,
 saveUninitialized: true
}));

express-session을 미들웨어로 등록한다. sessionID를 발급할 때 username이나 password같은 정보만으로 만들어 발급한다면 외부에서 이 정보를 알아낼 가능성이 있다. 이는 심각한 보안사고이다. 그래서 추가적으로 secret키를 sessionID 생성에 참고해서 이 sessionID를 역으로 해석해서 원래 정보를 알아내기 힘들도록 한다.

이렇게 등록을 하면 Server에 접속했을 때

와 같은 sessionid가 발급된다.

2. Login

로그인을 구현하기 위해서 Client에서 ID와 Password가 Server에 오면 이를 user database에 있는 user정보와 비교해서 제대로 된 유저인지 확인한다. 만약 제대로 된 user라면 sessionId를 발급해주면 된다.

router.post('/login_process', (req, res) => {
  const userData = fs.readFileSync(`${__dirname}/../data/user.json`, (e) => {if(e) console.log(e)});
  let loginSuccess = false;
  let userID;
  JSON.parse(userData.toString()).users.some(x => {
    if(x.id == req.body.id && x.password && req.body.password) {
      loginSuccess = true;
      userID = x.id;
      return true;
    }  
  });

  if(loginSuccess) {
    console.log("로그인 성공!");
    req.session.sessionID = userID;
  } else {
    console.log("로그인 실패");
  }
  res.redirect('/');
})

login이 성공하면 req.session에 원하는 property에 원하는 정보를 담아 보낸다. 위 코드는 userID를 담아 어떤 user가 접속했는지 확인할 수 있도록 했다.

3. sessionID로 Server에서 어떤 Client인지 식별

client에게 고유한 sessionID가 발급되었기 때문에 이 sessionID값으로 Server에 접근하면 서버는 지금 접근한 게 누군지 알 수 있다.

router.get('/rightLoginCheck', (req, res) => {
  if(req.session.sessionID) {
    console.log(req.session.sessionID, "가 접속했습니다");
  } else {
    console.log("잘못된 유저");
  }
  res.redirect("/");
})

4. session삭제, 로그아웃 기능 구현

로그아웃은 Client가 가지고 있는 sessionid쿠키를 삭제하는 것과 server에서 유지하고 있는 session값을 지워주는 것으로 구현할 수 있다.

router.get('/logout_process', (req, res) => {
  if(req.session.sessionID) {

    console.log(`${req.session.sessionID}가 로그아웃했습니다.`);
    req.session.destroy((err) => {
      if(err) {
        console.log(err);
      }
    })
  }

  res.redirect('/');
})

참고

https://velopert.com/406

'웹 프로그래밍 > node.js' 카테고리의 다른 글

Express 미들웨어 매우 간단한 개념  (0) 2020.09.03
Express 구조  (0) 2020.08.31

개요

데이터베이스를 외부에(AWS나 NCP, 혹은 다른 PC나 가상환경 등등) 두고 그 곳에 접속하는 환경을 만들 때 고려해야 할 것들을 정리했다.

고려해야할 4가지 그림

1. 접속 가능 포트 설정

먼저 외부 서비스에 접속하기 위해서 Mysql로 접속하기 위한 포트를 포트포워딩 해주어야 한다. NCP에서는 ACG 규칙에 추가해주는 것으로 설정이 가능하다.

2. Mysql에 접속 가능한 IP주소

Mysql에 접속가능한 IP주소는 default로 localhost로 지정되어 있다. 이를 수정하기위해 다음과 같은 경로에 있는 파일을 열어서 bind-address를 localhost에서 0.0.0.0(전역) 으로 수정해주거나 접근가능하게 하고싶은 IP주소를 입력해서 외부에서 접속 가능하게 할 수 있다.

/etc/mysql/mysql.conf.d/mysqld.cnf

  • 위 사진 맨 아래의 bind-address의 값이 초기에 127.0.0.1로 되어있다. 이 것을 원하는대로 바꾸면 된다.

3. user에 접속가능한 IP주소 설정

위 그림을 예로 들면 userA에 접근가능한 IP주소는 127.0.0.1인데 2.2.2.2인 IP에서 접근을 시도해서 user에 로그인 할 수 없다. 로그인 하고싶은 IP에 접속 가능하도록 추가해준다.

CREATE USER ‘userA’@’%’ IDENTIFIED BY ‘user_password’;

4. 접근 가능한 database인지 확인

user가 접근하고자 하는 database의 접근권한이 없으면 접근할 수 없다. 아래와 같은 명령어를 통해 접근 권한을 부여하자.

grant all privileges on 'DB 이름'.'Table 이름' to ‘root’@‘%’ identified by ‘root의 패스워드’

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

데이터베이스 MYSQL 실습  (0) 2017.12.28
데이터베이스 MYSQL 이론  (0) 2017.12.23
PHP 실습  (0) 2017.12.22
JavaScript 실습  (0) 2017.12.21
JavaScript 실습  (0) 2017.12.20

문제 링크

풀이

void makePossibleCase() {
  for(int depth=1;depth<=n/2;++depth) {
    dfs(0, 0, depth);
  }
}

void processing() {
  input();
  makePossibleCase();
  cout << ans << endl;
}

makePossibleCase();

문제를 해결하기 위해서 경우의 수를 만들어 내야한다. 경우의 수는 한팀이 2명이상이 되도록 팀을 구성해야 한다. 한 팀에 만약 2명을 배정해줬다면 나머지가 다 다른 팀으로 가게 되므로 다른 팀에 n-2가 배정된다. 이런식으로 3이면 n-3 ... 으로 한팀에 n/2까지만 할당한 경우까지 진행하면 풀어낼 수 있다.

아래는 depth≤n/2, 위는 depth<n이다. 눈에띄게 시간차이가 난다.

경우의 수 만들어내기 - dfs활용

bool visited[21];
void dfs(int depth, int nowidx, int maxDepth) {
  if(depth == maxDepth) {
    checkTeamScore();
    return;
  }

  for(int i=nowidx;i<n;++i) {
    if(visited[i] == false) {
      visited[i] = true;
      oneCase.push_back(i);
      dfs(depth+1, i+1, maxDepth);
      oneCase.pop_back();
      visited[i] = false;
    }
  }
}
  • oneCase : 한 팀을 구성하는 index가 담길 vector

한팀에 배정할 팀원 수가 될 때까지 경우의 수를 만들어낸다. visited를 통해 이미 뽑아낸 것은 다시 뽑지 않도록 하고 nowidx를 통해 이미 뽑은 index보다 작은 값은 뽑지 않도록 해서 조합을 구현했다. 원하는 만큼 팀원을 뽑았다면 checkTeamScore()를 통해 답을 구해낸다.

checkTeamScore();

int ans = 999999;
void checkTeamScore() {
  leftTeamIdxClear();
  for(int j=0;j<oneCase.size();++j) {
    leftTeamIdx[oneCase[j]] = true;
  }
  ans = min(ans, calTeamScore());
}

왼쪽 팀에 배정된 인덱스들을 leftTeamIdx에 체크해준다. 이 값을 통해 왼쪽팀과 오른쪽 팀을 나누게 된다. 체크해준 후 calTeamScore()를 통해 두 팀의 각 값을 계산해내고 ans를 최신화해준다.

calTeamScore();

int calTeamScore() {
  int leftScore = 0;
  int rightScore = 0;
  for(int i=0;i<n;++i) {
    for(int j=i+1;j<n;++j) {
      if(leftTeamIdx[i] && leftTeamIdx[j]) {
        leftScore += table[i][j];  
      }
      if(!leftTeamIdx[i] && !leftTeamIdx[j]) {
        rightScore += table[i][j];  
      }
    }
  }

  return abs(leftScore - rightScore);
}

for문 두 개를 돌면서 두 인덱스가 모두 왼쪽 팀이면 그 값을 leftScore에, 둘다 오른쪽 팀이면 rightScore에 더하도록 했다. n제한이 20이므로 n^2 시간복잡도가 큰 부담이 없다.

코드

#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>

using namespace std;

/*
  인원수 달라도 됨
  1. 뽑는 경우의 수 생성
  2. 팀 만들어서 차이 보기
  - 반복
*/
int table[21][21];
int n;

void scorePreprocessing() {
  for(int i=0;i<n;++i) {
    for(int j=i+1;j<n;++j) {
      table[i][j] += table[j][i];
    }
  }
}

void input() {
  cin >> n;
  for(int i=0;i<n;++i) {
    for(int j=0;j<n;++j) {
      cin >> table[i][j];
    } 
  }
  scorePreprocessing();
}

vector<vector<int> > cases;
vector<int> oneCase;

bool leftTeamIdx[21];
void leftTeamIdxClear() {
  memset(leftTeamIdx, false, sizeof(leftTeamIdx));
}

int calTeamScore() {
  int leftScore = 0;
  int rightScore = 0;
  for(int i=0;i<n;++i) {
    for(int j=i+1;j<n;++j) {
      if(leftTeamIdx[i] && leftTeamIdx[j]) {
        leftScore += table[i][j];  
      }
      if(!leftTeamIdx[i] && !leftTeamIdx[j]) {
        rightScore += table[i][j];  
      }
    }
  }

  return abs(leftScore - rightScore);
}

int ans = 999999;
void checkTeamScore() {
  leftTeamIdxClear();
  for(int j=0;j<oneCase.size();++j) {
    leftTeamIdx[oneCase[j]] = true;
  }
  ans = min(ans, calTeamScore());
}

bool visited[21];
int recodeDepth = 0;
void dfs(int depth, int nowidx, int maxDepth) {
  if(depth == maxDepth) {
    if(recodeDepth != depth) {
      recodeDepth = depth;
    }
    checkTeamScore();
    return;
  }

  for(int i=nowidx;i<n;++i) {
    if(visited[i] == false) {
      visited[i] = true;
      oneCase.push_back(i);
      dfs(depth+1, i+1, maxDepth);
      oneCase.pop_back();
      visited[i] = false;
    }
  }
}

void makePossibleCase() {
  for(int depth=1;depth<=n/2;++depth) {
    dfs(0, 0, depth);
  }
}

void processing() {
  input();
  makePossibleCase();
  cout << ans << endl;
}

int main() {
  processing();
}

문제 링크

풀이

eraseBlock();
rootedClusterCheck();
flyClusterDown();

eraseBlock();

왼쪽, 오른쪽에서 막대기를 던질 때 해당하는 block을 제거하는 로직.

 

rootedClusterCheck();

rooted → 땅에 박혀있는 이라는 의미로 변수명 지었다. 땅(row == 0) 까지 연결되어 있는 클러스터는 땅으로 떨어지지 않기 때문에 먼저 땅과 연결되어 있는 클러스터들을 체크해줬다.

체크한 방법은 row == 0에 있는 것 중에서 x가 있으면 그걸 시작점으로 dfs를 돌면서 연결되어 있는 것들을 체크했다.

 

flyClusterDown();

앞에서 땅과 연결되어 있는 클러스터들은 체크가 됐다. 그렇다면 입력 테이블에서 'x'이면서 땅과 연결된 클러스터로 체크되지 않은 것은 모두 공중에 있는 클러스터일 것이다. 문제조건에 두 개 이상의 클러스터는 공중에 있지 않다고 했으므로 해당하는 x를 만나면 거기서 dfs를 통해 공중에 있는 클러스터들을 체크해줬다.

그리고 이 클러스터를 땅이나 만나는 미네랄에 연결시켜줘야 한다. 이 클러스터는 전체적으로 똑같은 col만큼 아래로 하강하는데 이 값을 효율적으로 구하기 위해 dfs돌면서 각 column의 가장 작은 row값을 따로 저장해 뒀다.

int findDownLength() {
  int minLength = 9999999;
  for(int j=0;j<c;++j) {
    if(columnList[j] == 999999) continue;
    for(int i=0;i<r;++i) {
      if(table[i][j] == 'x') {
        if(i == columnList[j]) {
          minLength = min(minLength, i);
          break;
        } else {
          minLength = min(columnList[j] - i - 1, minLength);
        }
      }
    }
  }
  return minLength;
}

이 값들은 위와 같은 코드에 사용되는데 땅부터 row를 증가시키면서 찾다가 테이블에 'x'를 만났다면 두 가지 경우중 하나이다.

첫 번째는 공중에 떠있는 자기자신을 만났을 때,

두 번째는 다른 땅과 연결된 클러스터를 만났을 때이다.

전자는 자기를 만났기 때문에 자기 위쪽은 탐색할 필요 없다. 그래서 클러스터를 몇칸 땅으로 이동시킬지에 대한 변수인 minLength를 자기 row값으로 min(minLength, i)로 최신화 시켜주었다.

후자는 지금 만난 x보다 더 위에도 x가 존재할 수 있고, 그 때에는 minLength가 더 작아지기 때문에 추가적인 탐색이 필요해 break를 걸지 않았다.

이렇게 몇칸 아래로 내릴지 구한다음 공중에 떠있는 클러스터들은 모두 체크가 되어 있기 때문에 그 칸만큼 내려준다.

이 3가지 과정을 N번 진행하면 문제가 풀린다.

코드

#include <iostream>
#include <vector>

using namespace std;

char table[101][101];

int r, c;
bool isrootedCluster[101][101];
void rootedClusterClear() {
  for(int i=0;i<r;++i) {
    for(int j=0;j<c;++j) {
      isrootedCluster[i][j] = false;
    }
  }
}

vector<int> stickThrowRow;

void input() {
  cin >> r >> c;
  for(int i=0;i<r;++i) {
    for(int j=0;j<c;++j) {
      cin >> table[r-i-1][j];
    }
  }
  int n;
  cin >> n;
  int temp;
  for(int i=0;i<n;++i) {
    cin >> temp;
    stickThrowRow.push_back(temp); 
  }
}

bool blockErase(int row, int col) {
  if(table[row][col] == 'x') {
    table[row][col] = '.';
    return true;
  }
  return false;
}

void eraseBlock(int throwIdx, bool isRightToLeft) {
  if(isRightToLeft) {
    for(int i=0;i<c;++i) {
      if(blockErase(throwIdx, i)) return;
    }
  } else {
    for(int i=c-1;i>=0;--i) {
      if(blockErase(throwIdx, i)) return;
    }
  }
}

int dx[] = {0,0,1,-1};
int dy[] = {1,-1,0,0};
bool dfsRangeChk(int nx, int ny) {
  return (0 <= nx && nx < c && 0 <= ny && ny < r);
}

bool dfsVisited[101][101];
void dfsVisitedClear() {
  for(int i=0;i<r;++i) {
    for(int j=0;j<c;++j) {
      dfsVisited[i][j] = false;
    }
  }
}

bool columnCheckNeeded = false;
int columnList[101];
void clearColumnList() {
  for(int i=0;i<101;++i) {
    columnList[i] = 999999;
  }
}

void dfs(int row, int col) {
  for(int i=0;i<4;++i) {
    int nx = col + dx[i];
    int ny = row + dy[i];
    if(dfsVisited[ny][nx] == false && dfsRangeChk(nx, ny)) {
      if(table[ny][nx] == 'x'){
        if(columnCheckNeeded) {
          columnList[nx] = min(columnList[nx], ny);
        }
        dfsVisited[ny][nx] = true;
        dfs(ny, nx);
      }
    }
  }
}

void pasteRootedResult() {
  for(int i=0;i<r;++i) {
    for(int j=0;j<c;++j) {
      if(dfsVisited[i][j] == true) {
        isrootedCluster[i][j] = true;
      }
    }
  }
}

void rootedClusterCheck() {
  for(int i=0;i<c;++i) {
    if(isrootedCluster[0][i] == false && table[0][i] == 'x') {
      dfsVisitedClear();
      isrootedCluster[0][i] = true;
      dfs(0, i);
      pasteRootedResult();
    } 
  }
}

void findflyCluster(int i, int j) {
  dfsVisitedClear();
  dfsVisited[i][j] = true;
  columnList[j] = i;
  dfs(i, j);
}

int findDownLength() {
  int minLength = 9999999;
  for(int j=0;j<c;++j) {
    if(columnList[j] == 999999) continue;
    for(int i=0;i<r;++i) {
      if(table[i][j] == 'x') {
        if(i == columnList[j]) {
          minLength = min(minLength, i);
          break;
        } else {
          minLength = min(columnList[j] - i - 1, minLength);
        }
      }
    }
  }
  return minLength;
}

void downCluster() {
  int downLength = findDownLength();
  for (int j = 0; j < c; ++j) {
    for (int i = 0; i < r; ++i) {
      if(dfsVisited[i][j] == true) {
        table[i][j] = '.';
        table[i-downLength][j] = 'x';
      }
    }
  }
}

void flyClusterDown() {
  for(int i=0;i<r;++i) {
    for(int j=0;j<c;++j){
      if(isrootedCluster[i][j] == false && table[i][j] == 'x') {
        clearColumnList();
        columnCheckNeeded = true;
        findflyCluster(i, j);
        columnCheckNeeded = false;
        downCluster();
        return;
      }
    }
  }
}

void processing() {
  for(int i=0;i<stickThrowRow.size(); ++i) {
    eraseBlock(stickThrowRow[i]-1, i%2==0);
    rootedClusterCheck();
    flyClusterDown();
    rootedClusterClear();
  }
}

void printAnswer() {
  for(int i=0;i<r;++i) {
    for(int j=0;j<c;++j) {
      cout << table[r-i-1][j];
    }
    cout << '\n';
  }
}

int main(void) {
  // freopen("b2933.txt", "r", stdin);
  ios_base::sync_with_stdio(false);
  cin.tie(NULL);
  input();
  processing();
  printAnswer();
}

'알고리즘' 카테고리의 다른 글

백준 2916 자와 각도기 c++  (0) 2020.09.15
백준 2210 숫자판 점프 c++  (0) 2020.09.15
백준 2776 CPP, js  (0) 2020.09.04
[javascript] b1072.js  (0) 2020.09.03
[프로그래머스] 정수삼각형 c++ 재귀로 풀기  (0) 2020.07.04

+ Recent posts