궤도

[백준] 17471번 : 게리맨더링 본문

💻 현생/⛓ 알고리즘

[백준] 17471번 : 게리맨더링

영이오 2021. 6. 27. 19:45

문제

 


풀이

 

할게 많아보이는데 그렇게 많진 않다.

 

1. 두 선거구로 나눈다.

2. 두 선거구의 인구 차이를 구한다.

3. 두 선거구를 올바르게 나눴다면 인구 차이의 최솟값을 갱신한다.

 

2번과 3번의 순서가 바뀌어야 하지 않나?라고 생각할 수도 있다.

근데 인구 차이를 먼저 구하고 그 값이 현재의 최솟값보다 크거나 같다면 3번을 실행하지 않을 것이다.

그럼 불필요한 연산을 하지 않으니 시간이 좀 줄어들 것이다.

 

두 선거구를 나누는 조합 연산은 비트마스킹으로 했다.


소스코드

 

#include <iostream>
#include <vector>

using namespace std;

vector<bool> split, visited;
vector<int> population;
vector<vector<int>> graph;

int calcDiff() { //두 선거구의 인구 차이
    int blue = 0, red = 0;
    for (int i = 1; i < split.size(); i++) {
        if (split[i])
            blue += population[i];
        else
            red += population[i];
    }
    return abs(blue - red);
}

void dfs(int cur) {
    visited[cur] = true; //방문 처리
    for (int i = 0; i < graph[cur].size(); i++) {
        int node = graph[cur][i];
        if (split[node] == split[cur] && !visited[node]) //같은 선거구이면서 방문하지 않은 곳일 때
            dfs(node);
    }
}

int main() {
    int N, num, input, result = 10000;

    cin >> N;
    graph.assign(N + 1, vector<int>(0));
    population.assign(N + 1, 0);
    split.assign(N + 1, false);
    for (int i = 1; i <= N; i++)
        cin >> population[i];
    for (int i = 1; i <= N; i++) {
        cin >> num;
        while (num--) {
            cin >> input;
            graph[i].push_back(input);
        }
    }
    for (int i = 1; i < (1 << N) - 1; i++) { //비트마스크 조합
        for (int j = 0; j < N; j++) //각 선거구로 나눠줌
            split[j + 1] = (i & (1 << j)) != 0;
        int diff = calcDiff(); //인구 수 차이
        if (diff >= result) //최솟값보다 크다면 탐색할 필요 없음
            continue;

        visited.assign(N + 1, false);
        int cnt = 0; //dfs를 돈 횟수
        for (int j = 1; j <= N && cnt < 3; j++) { //각 선거구가 연결됐다면 dfs가 2번만 실행돼야 함
            if (!visited[j]) {
                cnt++;
                dfs(j);
            }
        }
        if (cnt == 2) //각 선거구가 연결됐다면 갱신
            result = diff;
    }
    cout << ((result == 10000) ? -1 : result); //출력
}

전체 소스코드다.

 

vector<bool> split, visited;
vector<int> population;
vector<vector<int>> graph;

split으로 각 지역이 어떤 선거구에 들어갔는지 표시하는데 쓰고, visited는 dfs에 쓸 것이다.

population에 각 지역의 인구수를 저장하고, 지역의 연결관계는 graph에 저장한다.

 

    cin >> N;
    graph.assign(N + 1, vector<int>(0));
    population.assign(N + 1, 0);
    split.assign(N + 1, false);
    for (int i = 1; i <= N; i++)
        cin >> population[i];
    for (int i = 1; i <= N; i++) {
        cin >> num;
        while (num--) {
            cin >> input;
            graph[i].push_back(input);
        }
    }

입력을 받는 부분이고...

 

    for (int i = 1; i < (1 << N) - 1; i++) { //비트마스크 조합
        for (int j = 0; j < N; j++) //각 선거구로 나눠줌
            split[j + 1] = (i & (1 << j)) != 0;
        int diff = calcDiff(); //인구 수 차이
        if (diff >= result) //최솟값보다 크다면 탐색할 필요 없음
            continue;

        visited.assign(N + 1, false);
        int cnt = 0; //dfs를 돈 횟수
        for (int j = 1; j <= N && cnt < 3; j++) { //각 선거구가 연결됐다면 dfs가 2번만 실행돼야 함
            if (!visited[j]) {
                cnt++;
                dfs(j);
            }
        }
        if (cnt == 2) //각 선거구가 연결됐다면 갱신
            result = diff;
    }

비트마스크로 구한 각 조합에 대해 연산을 한다.

모든 지역이 한 구역에 속한 경우는 제외해야 하기 때문에, 0과 111...1111을 제외했다.

 

        for (int j = 0; j < N; j++) //각 선거구로 나눠줌
            split[j + 1] = (i & (1 << j)) != 0;

대충 101100이면 1, 2, 5번 지역은 빨간색이고, 3, 4, 6번 지역은 파란색으로 표시하는 것이다.

 

        int diff = calcDiff(); //인구 수 차이
        if (diff >= result) //최솟값보다 크다면 탐색할 필요 없음
            continue;

이 조합에 대해 인구 수의 차이를 구하고, 이게 현재의 최솟값이 result면 더이상 살펴보지 않는다.

 

int calcDiff() { //두 선거구의 인구 차이
    int blue = 0, red = 0;
    for (int i = 1; i < split.size(); i++) {
        if (split[i])
            blue += population[i];
        else
            red += population[i];
    }
    return abs(blue - red);
}

인구 수의 차이는 그냥 이렇게 구한다.

 

        visited.assign(N + 1, false);
        int cnt = 0; //dfs를 돈 횟수
        for (int j = 1; j <= N && cnt < 3; j++) { //각 선거구가 연결됐다면 dfs가 2번만 실행돼야 함
            if (!visited[j]) {
                cnt++;
                dfs(j);
            }
        }

이제 dfs로 선거구를 잘 나눴는지 확인한다.

제대로 나눴다면 dfs는 2번만 실행되어야 한다. (파랑 1번, 빨강 1번)

 

void dfs(int cur) {
    visited[cur] = true; //방문 처리
    for (int i = 0; i < graph[cur].size(); i++) {
        int node = graph[cur][i];
        if (split[node] == split[cur] && !visited[node]) //같은 선거구이면서 방문하지 않은 곳일 때
            dfs(node);
    }
}

dfs는 간단하게 구현했다.

 

        if (cnt == 2) //각 선거구가 연결됐다면 갱신
            result = diff;

그래서 dfs가 2번만 실행된 것까지 확인했다면 result를 갱신한다.

 

    cout << ((result == 10000) ? -1 : result); //출력

출력하면 끝

Comments