APIO 2012 경비병(Guard)

#include <cstdio>
#include <algorithm>
#include <vector>

using namespace std;
const int MAX = 100020;

struct report {
    int s, e;

    report() {}
    report(int s, int e) : s(s), e(e) {}

    bool operator < (const report &t) const {
        if(s == t.s) return e < t.e;
        return s < t.s;
    }
};

int n, m, k, yFull, nFull;
report yes[MAX], no[MAX];

void input(){
    scanf("%d%d%d", &n, &k, &m);

    int i;
    for(i = 0; i<m; i++){
        int ts, te, c;
        scanf("%d%d%d", &ts, &te, &c);

        //닌자가 없다는 보고는 no에, 있다는 보고는 yes에 저장
        if(c == 0) no[nFull++] = report(ts, te);
        else yes[yFull++] = report(ts, te);
    }
}

bool none[MAX];
int cnt, lFind[MAX], rFind[MAX];

void init(){
    //보고들을 좌표 순으로 정렬
    sort(yes, yes+yFull);
    sort(no, no+nFull);

    int i, now;

    //닌자가 있다는 보고들을 정리. yes 배열을 스택으로 사용한다.
    now = 1;
    for(i = 1; i<yFull; i++){
        /*
        (s1, e1) (s2, e2)일 때 s1 <= s2 && e2 <= e1이면
        첫 번쨰 보고를 삭제하고 두 번째 보고만 삽입
        */
        while(yFull && yes[now-1].e >= yes[i].e)
            now--;
        yes[now++] = yes[i];
    }
    yFull = now;

    //닌자가 없다는 보고들을 정리
    cnt = n;
    int j = 1;
    for(i = 0; i<nFull; i++){
        if(j < no[i].s) j = no[i].s;
        for(; j <= no[i].e; j++){
            //닌자가 확실히 없는 덤불은 none 배열에 체크
            none[j] = 1;
            cnt--;
        }
    }
    //루프가 끝나면 cnt는 '닌자가 있을 가능성이 있는 덤불의 수'가 된다.

    /*
    lFind[i]는 번호가 i 이하인 덤불 중
    '닌자가 있을 가능성이 있는 덤불' 중 가장 번호가 큰 덤불의 번호
    */
    for(i = 1; i<=n; i++){
        if(!none[i]) lFind[i] = i;
        else lFind[i] = lFind[i-1];
    }

    /*
    rFind[i]는 번호가 i 이상인 덤불 중
    '닌자가 있을 가능성이 있는 덤불' 중 가장 번호가 작은 덤불의 번호
    */
    for(i = n; i>=1; i--){
        if(!none[i]) rFind[i] = i;
        else rFind[i] = rFind[i+1];
    }
}

int left[MAX], lFull, right[MAX], rFull;
vector < int > res;

void solve(){
    int i;
    //'닌자가 숨어있을 가능성이 있는 덤불'이 k개와 일치한다면 k개 전부가 답.
    if(cnt == k){
        for(i = 1; i<=n; i++)
            if(lFind[i] == i)
                res.push_back(i);
    } else {
        int last;

        /*
        k개보다 많다면, 탐욕 알고리즘을 사용해 '보고를 만족하는 최소의 닌자 수'를 구한다.

        닌자들을 놓을 수 있는 가장 오른쪽 칸에 놓아서 채우는 경우, 가장 왼쪽 칸에 놓아서 채우는 경우를 구한다.
        조금 생각해보면 오른쪽에서부터 채울 때와 왼쪽에서 채울 때의 닌자 수가 같다는 것을 알 수 있다. (lFull == rFull)

        닌자들을 최소로 사용하여 채울 때 i번째 닌자가 있을 수 있는 가장 왼쪽 좌표가 left[i], 가장 오른쪽 좌표가 right[i]이다.
        */
        last = 0;
        for(i = 0; i<yFull; i++){
            if(last < yes[i].s){
                left[lFull++] = lFind[yes[i].e];
                last = lFind[yes[i].e];
            }
        }

        last = n+1;
        for(i = yFull-1; i>=0; i--){
            if(last > yes[i].e){
                right[rFull++] = rFind[yes[i].s];
                last = rFind[yes[i].s];
            }
        }

        //닌자를 최소로 사용했을 때 k명인 경우
        if(lFull == k){
            for(i = 0; i<lFull; i++){
                /*
                k명의 닌자 중 존재할 수 있는 가장 왼쪽 좌표와 가장 오른쪽 좌표가 같은 닌자는 위치가 고정되므로 답이 된다.
                그렇지 않은 닌자들은 최소 좌표와 최대 좌표 중 다른 칸에 존재할 수 있는 가능성이 있기 때문에 답이 될 수 없다.
                */
                if(left[i] == right[rFull-1-i]){
                    res.push_back(left[i]);
                }
            }
        //닌자를 최소로 사용했을 때 k명보다 작은 경우 (보고를 만족하는 배치가 존재하기 때문에 k보다 클 수는 없다.)
        } else {
            for(i = 0; i<yFull; i++){
                /*
                닌자를 발견했다는 보고에 대해, 보고 범위 이내에 '닌자가 숨어 있을 가능성이 있는 덤불'이 하나인 경우만 답이 된다.

                그렇지 않은 덤불들은 닌자가 숨어 있을 가능성이 있는 덤불 중
                그 덤불의 바로 왼쪽 덤불과 바로 오른쪽 덤불을 대체해서 선택하면
                그 덤불을 선택하지 않고도 모든 보고를 만족시킬 수 있다.

                이 경우 닌자의 총 인원 수가 증가할수도 있지만, 현재 닌자 수가 k보다 작기 때문에 상관 없다.
                */
                if(lFind[lFind[yes[i].e]-1] < yes[i].s)
                    res.push_back(lFind[yes[i].e]);
            }
        }
    }

    //정답을 출력한다.
    if(res.empty()) puts("-1");
    else {
        for(i = 0; i<res.size(); i++)
            printf("%d\n", res[i]);
    }
}

int main(){
    input();

    init();
    solve();

    return 0;
}

APIO 2012 경비병(Guard) 코드 및 주석입니다.

댓글 남기기