#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) 코드 및 주석입니다.