문제 링크

풀이

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

A. Ahahahahahahahaha

n / 2 이하로만 뽑으면 됐다. 0과 1밖에 안나오기 때문에 0이 n / 2 이상 개수가 있었으면 n / 2개만큼의 0을 출력해주면 되고 0이 n / 2 미만이었다면 1이 n / 2 + 1 개 있다는 것이므로 1을 n / 2 + 1 만큼 출력하면 되는 문제였다.

B. Big Vova

안그래도 영어라 읽기 힘든데 무려 한 문단이 문제와 관련없었다. 맨 앞에 오는 숫자는 반드시 a 배열중에서 가장 큰 숫자여야 했다. GCD(b1)에서 그 수 자체가 c1이 되는데 사전순으로 가장 크게 될려면 가장 큰 숫자가 왔어야 하기 때문이다.

처음에 풀면서 n^2에 gcd까지 구하는 풀이가 생각났는데 n제한이 10^5인줄 알고 이 방법을 쓰지 않고 다른 걸 찾다가 시간을 많이 썼다. 10^3인걸 잘 봤으면 이렇게 풀고 빨리 끝냈을 수 있을 것 같다.

C. Chocolate Bunny

크기가 다른 두 숫자를 순서 바꿔서 한번 씩 mod연산 하면 둘 중 작은 값이 두 숫자 중 작은 값이다. 이를 이용해서 풀 수 있었다.

+ Recent posts