2017년 11월 30일 목요일

1109 섬

1109 섬 https://www.acmicpc.net/problem/1109

섬들은 'x'로, 물은 '.'로 표시된 지도가 주어진다.
섬은 가로,세로,대각선이 연결되있으면 같은 섬이고 물은 가로,세로만 연결할 수 있다.
어떤 섬 A가 포함하고 있는 가장 높이가 높은섬의 높이가 K라면 A의 높이는 K+1이다.
높이가 0인섬부터 가장높은 섬의 높이까지 차례대로 개수를 구하는 문제이다.

한달전쯤 시도했다가 실패한 문제였다.
예전에 BFS를 약 한달동안 판적이 있어서 자신있었는데 
이 문제를 접하고나니 아직 갈길이 멀다고 생각이 든다.

우선 바깥쪽 섬에서 안쪽 섬으로 들어오는 형식으로 풀어야지만 풀 수 있다.

처음에는 지도의 윤곽에 존재하는 물에서 시작을 해야한다.
문제예시처럼 지도의 모든 윤곽이 섬에의해 둘러싸여있다면 물을 queue에 넣을 수 없다.
이러한 경우에는 따로 예외처리를 해주었다.
물을 queue에 넣고 BFS를 진행하다가 섬을 만나면 물의 근원지 섬(?)에서 만난 섬을 연결시켜 주었다.
이렇게 연결된 인접리스트가 구성되면 DFS로 원하는 답을 한번에 구했다.
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
int N, M;
char map[51][51];
int visit[51][51];
bool adjmat[2555][2555];
vector<int> adj[2555];
queue<int> isl, wat;
int cnt = 1;
int dy[] = { -1,0,0,1,1,1,-1,-1 };
int dx[] = { 0,1,-1,0,-1,1,1,-1 };
vector<int> pos[2555];
vector<int> curr;
bool chk[2555];
void island(int iy, int ix) {
    visit[iy][ix] = cnt;
    isl.push(iy*+ ix);
    pos[cnt].push_back(iy*+ ix);
    while (!isl.empty()) {
        int y = isl.front() / M, x = isl.front() % M;
        isl.pop();
 
        for (int i = 0;i < 8;i++) {
            int ny = y + dy[i], nx = x + dx[i];
            if (ny < 0 || ny >= N || nx < 0 || nx >= M || visit[ny][nx] != 0 || map[ny][nx] == '.'continue;
            visit[ny][nx] = cnt;
            isl.push(ny*+ nx);
            pos[cnt].push_back(ny*+ nx);
        }
    }
}
void water(int iy, int ix, int here) {
    if (visit[iy][ix] != 0return;
    visit[iy][ix] = -1;
    wat.push(iy*+ ix);
    while (!wat.empty()) {
        int y = wat.front() / M, x = wat.front() % M;
        wat.pop();
 
        for (int i = 0;i < 4;i++) {
            int ny = y + dy[i], nx = x + dx[i];
            if (ny < 0 || ny >= N || nx < 0 || nx >= M) continue;
            if (map[ny][nx] == '.' && visit[ny][nx] == 0) {
                visit[ny][nx] = -1;
                wat.push(ny*+ nx);
            }
            else if (map[ny][nx] == 'x') {
                if (!adjmat[here][visit[ny][nx]]) {
                    adjmat[here][visit[ny][nx]] = true;
                    adj[here].push_back(visit[ny][nx]);
                    curr.push_back(visit[ny][nx]);
                //    printf("%d->%d\n", here, visit[ny][nx]);
                }
            }
        }
    }
}
void solve(int num) {
    queue<int> q;
    for (int n = 0;n < pos[num].size();n++) q.push(pos[num][n]);
    while (!q.empty()) {
        int y = q.front() / M, x = q.front() % M;
        q.pop();
 
        for (int i = 0;i < 4;i++) {
            int ny = y + dy[i], nx = x + dx[i];
            if (ny < 0 || ny >= N || nx < 0 || nx >= M) continue;
            if (map[ny][nx] == '.' && visit[ny][nx] == 0) {
                visit[ny][nx] = num;
                q.push(ny*+ nx);
            }
            else if (map[ny][nx] == 'x' && visit[ny][nx] != num) {
                if (!adjmat[num][visit[ny][nx]]) {
                    adjmat[num][visit[ny][nx]] = true;
                    adj[num].push_back(visit[ny][nx]);
                    curr.push_back(visit[ny][nx]);
                //    printf("%d->%d\n", num, visit[ny][nx]);
                }
            }
        }
    }
}
//void print() {
//    for (int n = 0;n < N;n++) {
//        for (int m = 0;m < M;m++)
//            printf("%2d ", visit[n][m]);
//        printf("\n");
//    }
//}
int ans[51];
int Max = 0;
int dfs(int here) {
    int ret = 0;
    for (int &next : adj[here]) {
        ret = max(ret, dfs(next) + 1);
    }
    if (here != 0) {
        ans[ret]++;
        Max = max(Max, ret);
    }
    return ret;
}
int main() {
    bool flag = true;
    scanf("%d%d"&N, &M);    
    for (int n = 0;n < N;n++) {
        scanf("%s"&map[n]);
        if (flag) {
            for (int m = 0;m < M;m++) {
                if (map[n][m] == 'x') flag = false;
            }
        }
    }
    if (flag) { printf("-1\n"); return 0; }
    for (int n = 0;n < N;n++)for (int m = 0;m < M;m++) {
        if (map[n][m] == 'x' && visit[n][m] == 0) {
            island(n, m);
            cnt++;
        }
    }
    for (int m = 0;m < M;m++) {
        if (visit[0][m] == 0 && map[0][m] == '.') {
            water(0, m, 0);
        }
        if (visit[N - 1][m] == 0 && map[N - 1][m] == '.') {
            water(N - 1, m, 0);
        }
    }
    for (int n = 1;n < N - 1;n++) {
        if (visit[n][0== 0 && map[n][0== '.') {
            water(n, 00);
        }
        if (visit[n][M - 1== 0 && map[n][M - 1== '.') {
            water(n, M - 10);
        }
    }
    if (curr.empty()) curr.push_back(visit[0][0]), adj[0].push_back(visit[0][0]);
    while (!curr.empty()) {
        vector<int> tmp;
        for (int &n : curr) {
            tmp.push_back(n);
        }
        curr.clear();
 
        for (int &n : tmp) {
            solve(n);
        }
    }
    dfs(0);
    for (int n = 0;n <= Max;n++) {
        printf("%d ", ans[n]);
    }
    return 0;
}
cs

2017년 11월 29일 수요일

1273 샷

1278 샷 https://www.acmicpc.net/problem/1273

N열의 맥주캔이 쌓여져있는 상태가 주어진다.
총으로 하나의 층을 없앨 수 있는데 맥주캔의 종류(검,회,흰)에따라 점수를 획득한다.
M개의 쿼리를 입력받아 해당 층을 없앨 때 점수를 구하는 문제다.

NM이 300,000이나 되고 하나의 열에 최대 3,000,000층 만큼의 맥주가 쌓일 수 있다.
쿼리를 받을때마다 일일이 처리하는건 절대 안되고 굉장히 크기가 큰만큼 메모리도 신경써야한다.

처음에는 segment treelazy propagation로 3,000,000층에서 각층의 점수를 누적해서 저장해주었다.
근데 long long 변수로 저장하고 이러한 segment tree를 2개 저장했으니 8*16*3,000,000 byte가 필요하였다.
그래서 메모리초과가 발생하였다.

좀 생각해보니 각층의 점수 누적을 segment tree로 할 필요없이 일반배열에 저장해서 누적합하면 될것같았다.
그리고 각 층의 쿼리계산은 아까 구현해놓은 segment tree로 갖다 썼다.

사실 메모리초과 발생해서 틀리고 나서 못풀거같은 느낌이 들었는데 차분히 생각하니 답이 나왔다.
또 배열을 int형으로 1200만 선언을 해본적은 처음이고 되는사실도 오늘 알았다.
풀려서 기분이 좋다.
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
int N, M;
int Plus[3= { 1,2,5 };
int p[300001];
int sum[3000000];
bool chk[3000000];
struct Segment {
    vector<int> tree;
    int size;
    Segment() {}
    Segment(int N) :size(N) { tree.resize(4 * N, 0); }
    int init(int node, int nl, int nr) {
        if (nl == nr) return tree[node] = 1;
        int mid = (nl + nr) >> 1;
        return tree[node] = init(node * 2, nl, mid) + init(node * 2 + 1, mid + 1, nr);
    }
    void init() {
        init(10size - 1);
    }
    int update(int idx, int val, int node, int nl, int nr) {
        if (nl > idx || nr < idx) return tree[node];
        if (nl == nr) return tree[node] += val;
        int mid = (nl + nr) >> 1;
        return tree[node] = update(idx, val, node * 2, nl, mid) + update(idx, val, node * 2 + 1, mid + 1, nr);
    }
    void update(int idx, int val) {
        update(idx, val, 10size - 1);
    }
    int find(int val, int node, int nl, int nr) {
        if (nl == nr) return nl;
        int mid = (nl + nr) >> 1;
        if (tree[node * 2>= val) return find(val, node * 2, nl, mid);
        else return find(val - tree[node * 2], node * 2 + 1, mid + 1, nr);
    }
    int find(int val) {
        return find(val, 10size - 1);
    }
};
int main() {
    scanf("%d"&N);
    Segment seg(3000000);
    seg.init();
    for (int n = 0;n < 3;n++) {
        for (int m = 0, in;m < N;m++) {
            scanf("%d"&in);
            sum[p[m]] += Plus[n];
            sum[p[m] + in] -= Plus[n];
            p[m] += in;
        }
    }
    for (int n = 0;n < 3000000;n++) {
        if (n != 0) sum[n] += sum[n - 1];
    }
    scanf("%d"&M);
    for (int m = 0, in;m < M;m++) {
        scanf("%d"&in);
        int idx = min(3000000 - 1, seg.find(in));
        if (!chk[idx]) {
            chk[idx] = true;
            printf("%d\n", sum[idx]);
            seg.update(idx, -1);
        }
        else printf("0\n");
    }
    return 0;
}
cs

2017년 11월 27일 월요일

5000 빵 정렬

5000 빵 정렬 https://www.acmicpc.net/problem/5000

1부터 N까지의 빵 번호가 주어진다.
세개의 빵을 한번에 뒤집을 수 있는데 가장 오른쪽에 있는 빵이 맨 왼쪽, 나머지는 오른쪽으로 한칸씩 밀려진다.
빵 순서를 사장이 원하는 순서로 바꿀 수 있는지 판단하는 문제다.

정답률이 85%정도 되는데 쉬운문제는 아니다.
직접 손으로 경우를 그려보니 사장이 원하는 순서의 첫번째 숫자부터 맞추면서 판단하면 해결이 가능해보였다.

두 가지 경우로 나뉘었다.
사장이 원하는 순서의 맨 앞에빵이 6번이다.
상근이의 6번빵은 5번째 index에 위치하는데 위와같이 6번빵을 당길 수 있다.
이때 사장과 상근이의 빵의 위치의 차이가 홀수인 경우에는 앞에 두 빵을 swap해서 저장해야한다.
짝수인 경우에는 swap할 필요없이 그대로 저장하면 된다.

여기까지는 생각해내는데 금방이였는데 자료구조를 어떻게 구현해야할지 한참걸렸다.
좀 복잡하지만 segment tree를 이용해 구현해냈다.
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace    std;
int N;
int src[100001];
int dst[100001];
int pos[100001];
struct Segment {
    vector<int> tree;
    int size;
    Segment() {}
    Segment(int N) :size(N) { tree.resize(4*N, 0); }
    int update(int idx, int val, int nl, int nr, int node) {
        if (idx < nl || idx > nr) return tree[node];
        if (nl == nr) return tree[node] += val;
        int mid = (nl + nr) >> 1;
        return tree[node] = update(idx, val, nl, mid, node * 2+ update(idx, val, mid + 1, nr, node * 2 + 1);
    }
    void update(int idx, int val) {
        update(idx, val, 0size - 11);
    }
    int query(int l, int r, int nl, int nr, int node) {
        if (r < nl || l > nr) return 0;
        if (l <= nl && nr <= r) return tree[node];
        int mid = (nl + nr) >> 1;
        return query(l, r, nl, mid, node * 2+ query(l, r, mid + 1, nr, node * 2 + 1);
    }
    int query(int l, int r) {
        return query(l, r, 0size - 11);
    }
    int find(int val, int node, int nl, int nr) {
        if (nl == nr) return  nl;
        int mid = (nl + nr) >> 1;
        if (tree[node * 2>= val) return find(val, node*2, nl, mid);
        else return find(val - tree[node * 2], node * 2 + 1, mid + 1, nr);
    }
    int find(int val) {
        return find(val, 10size - 1);
    }
};
int main() {
    scanf("%d"&N);
    Segment seg(N + 1);
    for (int n = 0;n < N;n++scanf("%d"&src[n]), pos[src[n]] = n, seg.update(n, 1);
    for (int n = 0;n < N;n++) {
        scanf("%d"&dst[n]);
        if (n >= N - 2continue;
        int idx = seg.query(0, pos[dst[n]]) - 1;
        seg.update(pos[dst[n]], -1);
        if ((idx) % 2 == 1) {
            int g1 = seg.find(1), g2 = seg.find(2);
            swap(pos[src[g1]], pos[src[g2]]);
            swap(src[g1], src[g2]);
        }
    }
    if (src[seg.find(1)] == dst[N - 2&& src[seg.find(2)] == dst[N - 1]) printf("Possible\n");
    else printf("Impossible\n");
    return 0;
}
cs
다른 분들은 BIT, fenwick tree를 이용해서 짧게 푸셨는데 코드 해석을 못하겠다.