2016년 11월 24일 목요일

MongoDB 설치 후 저장 디렉토리 변경 주의사항

MongoDB를 설치하기 위해 공식 도큐먼트(https://docs.mongodb.com/.../install-mongodb-on-ubuntu/)를 참고하였다.

정상적으로 설치된 것을 확인하고 이후에 기본 저장 디렉토리를 변경하기 위해서 /etc/mongod.conf 파일을 건드렸다.

# mongod.conf

# for documentation of all options, see:
#   http://docs.mongodb.org/manual/reference/configuration-options/

# Where and how to store data.
storage:
#  dbPath: /var/lib/mongodb
  dbPath: /data/mongodb
  journal:
    enabled: true
#  engine:
#  mmapv1:
#  wiredTiger:

# where to write logging data.
systemLog:
  destination: file
  logAppend: true
  path: /var/log/mongodb/mongod.log

# network interfaces
net:
  port: 27017
  bindIp: 127.0.0.1


#processManagement:

#security:

#operationProfiling:

#replication:

#sharding:

## Enterprise-Only Options:

#auditLog:

#snmp:

dbpath를 /data/mongodb 로 변경하고 /data/mongodb 디렉토리를 만들어줬다.

$ sudo mkdir /data && sudo mkdir /data/mongodb

그리고 다시 mongodb를 구동하면 실행에 실패한다.

$ sudo service mongod status
● mongod.service - High-performance, schema-free document-oriented database
   Loaded: loaded (/lib/systemd/system/mongod.service; disabled; vendor preset: enabled)
   Active: failed (Result: exit-code) since Thu 2016-11-24 04:49:20 UTC; 3s ago
     Docs: https://docs.mongodb.org/manual
  Process: 5335 ExecStart=/usr/bin/mongod --quiet --config /etc/mongod.conf (code=exited, status=100)
 Main PID: 5335 (code=exited, status=100)

mongodb에서 해당 디렉토리에 접근 권한이 없기 때문이다.

다음과 같이 권한을 추가해주면 정상적으로 구동된다.

$ chown mongodb:mongodb /data/mongodb

정상적으로 작동하는 모습

$ sudo service mongod status
● mongod.service - High-performance, schema-free document-oriented database
   Loaded: loaded (/lib/systemd/system/mongod.service; disabled; vendor preset: enabled)
   Active: active (running) since Thu 2016-11-24 04:50:04 UTC; 5s ago
     Docs: https://docs.mongodb.org/manual
 Main PID: 5420 (mongod)
    Tasks: 17
   Memory: 32.1M
      CPU: 130ms
   CGroup: /system.slice/mongod.service
           └─5420 /usr/bin/mongod --quiet --config /etc/mongod.conf

2016년 11월 1일 화요일

2022 - 사다리

https://www.acmicpc.net/problem/2022
https://uva.onlinejudge.org/...&problem=1507


\(a, b, c\) 가 주어지면 \(k\) 를 구해내는 문제이다.

피타고라스로 A, B를 구해서 기울기를 구하고, 두 직선의 교점 방정식을 이용했다. 그리고 교점의 y 위치가 \(c\) 가 되는 순간을 구하도록 이분 탐색을 했다.

우선 a가 포함된 직선을 g(x), b가 포함된 직선을 f(x)라 한다면 아래와 같은 정보가 나온다.

\(
\begin{cases}
A = \sqrt{a^2 - k^2} \\
B = \sqrt{b^2 - k^2}
\end{cases}
\)

\(
\begin{cases}
f(x) = \frac{B}{k}x \\
g(x) = -\frac{A}{k}x+A
\end{cases}
\)

두 직선이 만나는 순간을 \( (c_0, c) \)라 한다면 \( f(c_0) = g(c_0) = c \) 이여야 한다.

\(
\begin{align}
f(c_0) = & \frac{B}{k}c_0 = c \\
& c_0 = \frac{k \cdot c}{B}
\end{align}
\)

\(g(c_0) = g(\frac{k \cdot c}{B}) = c\) 가 나온다면 정답일것이다.

k를 찾는 과정에서 \(f(c_0) \gt c\) 라면 높이가 더 높은 좌표이므로 \(c_0\)을 줄여야한다는 뜻이다. k를 줄이면 \(c_0\)도 줄어든다.

k를 조정해서 만들어진 적당한 x로 f(x) = g(x)가 성립하는 지 확인하면서, 결과에 따라 k를 다시 조정하면 정답이 나온다.

k를 찾는 구간을 줄일 때 epsilon을 1e-5로 설정했을 때는 오답이었다. 이분탐색이 중간에 잘못되고 있나 생각에 구글링하다가 더 작은 결과까지 해보도록 1e-9 로 바꿨더니 정답을 맞았다. 너무 허무함

#include <bits/stdc++.h>
using namespace std;

#define INF 987654321

double a, b, c;

double g(double x, double k){
    double A = sqrt(a*a - k*k);
    return A - (A * x / k);
}

double f(double x, double k){
    return sqrt(b*b - k*k) * x / k;
}

int main(){
    while(~scanf("%lf %lf %lf", &a, &b, &c)){
        double l=0, r=min(a, b);
        
        while(r - l > 1e-9){
            double k = (l+r)/2.0;
            double c0 = k * c / sqrt(b*b - k*k);
            if(g(c0, k) > c){
                l = k;
            } else {
                r = k;
            }
        }
        
        printf("%.3lf\n", l);
    }
    return 0;
}

1939 - 중량제한

https://www.acmicpc.net/problem/1939

S에서 E로 가는 데, 한 번의 이동에서 옮길 수 있는 물품들의 중량의 최대값을 구한다.

문제를 읽고 왠지 이거 다익스트라처럼 그리디하게 풀면 풀리겠다 싶어서 그래프 몇개 끄적거렸는데 무한루프에 빠질것만 같은 공포에 휩싸였다.

dp를 적용하자니 다리의 중량제한 \(c(1 \le c \le 10^9)\) 라서 안되겠다싶었고.

그럼 스패닝 트리를 만들어볼까해서 프림을 생각했는데 몇 개 적어놨던 예제에서부터 안됐다.

그래도 트리로 만드는게 가장 정답같아서 크루스칼로 구현했는데 정답이었다.

크루스칼로 최대 스패닝 트리를 만들고, S에서 E로 가는 경로 중에 가장 중량제한이 작은 것을 출력한다.

#include <bits/stdc++.h>
using namespace std;

#define INF 987654321

typedef pair<int,int> ii;

struct edge {
    int u, v, d;
    bool operator < (const edge& e) const {
        return d < e.d;
    }
};

vector<edge> E;

int group[100001];

int find(int v){
    if(v != group[v])
        return (group[v] = find(group[v]));
    return v;
}

bool Union(int p, int q){
    p = find(p);
    q = find(q);
    if(p == q) return false;
    group[q] = p;
    return true;
}

vector<ii> adj[100001];

bool visit[100001];
int answer(int pos, int dst, int m = INF){
    if(pos == dst) return m;
    
    if(visit[pos]) return 0;
    visit[pos] = true;
    
    for(auto& v : adj[pos]){
        int r = answer(v.first, dst, min(v.second, m));
        if(r != 0) return r;
    }
    return 0;
}

int main(){
    int n, m;
    scanf("%d %d", &n, &m);
    while(m--){
        int a, b, c;
        scanf("%d %d %d", &a, &b, &c);
        E.push_back({a-1, b-1, c});
    }
    sort(E.rbegin(), E.rend());
    
    for(int i=0; i<n; ++i) group[i] = i;
    
    for(auto& e : E){
        if( Union(e.u, e.v) ){
            adj[e.u].push_back(ii(e.v, e.d));
            adj[e.v].push_back(ii(e.u, e.d));
        }
    }
    
    int src, dst;
    scanf("%d %d", &src, &dst);
    printf("%d", answer(src-1, dst-1));
    
    return 0;
}

다른 사람들 정답 코드를 보니까 다익스트라도 있었는데, 크루스칼로 푼 사람들 중에 하나를 보고 배웠다.

S에서 E로 가는 경로를 다시 탐색할 필요가 없이, S가 속한 집합과 E가 속한 집합이 같아지는 시점에 연결하는 간선의 중량이 "가능한 가장 큰 중량제한"이었다.

#include <bits/stdc++.h>
using namespace std;
 
#define INF 987654321
 
typedef pair<int,int> ii;
 
struct edge {
    int u, v, d;
    bool operator < (const edge& e) const {
        return d < e.d;
    }
};
 
vector<edge> E;
 
int group[100001];
 
int find(int v){
    if(v != group[v])
        return (group[v] = find(group[v]));
    return v;
}

int main(){
    int n, m;
    scanf("%d %d", &n, &m);
    while(m--){
        int a, b, c;
        scanf("%d %d %d", &a, &b, &c);
        E.push_back({a-1, b-1, c});
    }
    sort(E.rbegin(), E.rend());
     
    for(int i=0; i<n; ++i) group[i] = i;
     
    int src, dst;
    scanf("%d %d", &src, &dst);
     
    for(auto& e : E){
        group[find(e.u)] = find(e.v);
        if( find(src-1) == find(dst-1) ){
            printf("%d", e.d);
            break;
        }
    }
     
    return 0;
}

2016년 10월 31일 월요일

2146 - 다리 만들기

https://www.acmicpc.net/problem/2146

각 대륙마다 번호를 붙여서 동기적으로 대륙을 넓혀간다. 넓어가는 도중에 다른 대륙과 만난다면, 그 시점에 각 대륙이 넓혀진 거리만큼이 (부딪혔으므로) 연결된 거리이고, 그 중에 최소를 구한다.

동기적으로 넓혀가는 이유는 대륙 하나를 잡고 나머지와 비교하면 아래와 같은 경우가 생긴다.
대륙간의 거리를 모두 비교하면 \(O(N^2)\)이다. 물론 N이 최대 100까지이므로 모두 비교해도 문제될 건 없다.

#include <bits/stdc++.h>
using namespace std;

#define INF 987654321

int n, a[100][100];

int dy[]={-1,1,0,0};
int dx[]={0,0,-1,1};

bool visit[100][100];

bool giveId(int y, int x, const int id){
    if(y < 0 || y >= n || x < 0 || x >= n) return 0;
    
    if(visit[y][x] || a[y][x] == 0) return 0;
    visit[y][x] = true;
    
    a[y][x] = id;
    for(int i=0; i<4; ++i){
        giveId(y+dy[i], x+dx[i], id);
    }
    return 1;
}

struct land {
    int y, x, color;
};

int Distance[100][100];

int main(){
    scanf("%d ", &n);
    for(int i=0; i<n; ++i){
        for(int j=0; j<n; ++j){
            scanf("%d", &a[i][j]);
            Distance[i][j] = a[i][j] ? 0 : INF;
        }
    }
    
    int color = 1;
    for(int i=0; i<n; ++i){
        for(int j=0; j<n; ++j){
            color += giveId(i, j, color);
        }
    }
    
    queue<land> q;
    for(int i=0; i<n; ++i){
        for(int j=0; j<n; ++j){
            if(a[i][j] != 0) continue;
            
            for(int d=0; d<4; ++d){
                int ny=i+dy[d], nx=j+dx[d];
                if(ny < 0 || ny >= n || nx < 0 || nx >= n) continue;
                if(0 != a[ny][nx]){
                    Distance[ny][nx] = min(Distance[ny][nx], 1);
                    q.push({i, j, a[ny][nx]});
                    break;
                }
            }
        }
    }
    
    int answer = 0;
    int dist = 0;
    while(!q.empty()){
        ++dist;
        int qs = q.size();
        while(qs--){
            int y = q.front().y;
            int x = q.front().x;
            int c = q.front().color;
            q.pop();
            
            if(a[y][x]) continue;
            a[y][x] = c;
            Distance[y][x] = dist;
            
            for(int d=0; d<4; ++d){
                int ny=y+dy[d], nx=x+dx[d];
                if(ny < 0 || ny >= n || nx < 0 || nx >= n) continue;
                
                if(a[ny][nx] == 0){
                    q.push({ny, nx, c});
                } else if(a[ny][nx] != c){
                    int expected = dist + Distance[ny][nx];
                    if(answer) answer = min(answer, expected);
                    else answer = expected;
                }
            }
        }
        if(answer > 0) break;
    }
    
    printf("%d", answer);
    
    return 0;
}

2016년 10월 27일 목요일

2873 - 롤러코스터

https://www.acmicpc.net/problem/2873

홀수x짝수, 짝수x홀수, 홀수x홀수 크기에 대해서는 모든 경로를 방문하여 가장 오른쪽 아래 칸으로 갈 수 있다.

짝수x짝수 크기는 1칸 빼고 나머지는 다 방문해서 도착할 수 있는데, (홀수, 짝수) 또는 (짝수, 홀수)에 위치한 칸을 들리지 못한다. 빼는 1칸은 가장 작은 기쁨이 있는 칸을 선택하면 될 것이다.

여기까지는 어떻게 생각해냈는데, 경로를 어떻게 만들어야할지 몰라서 한참 고민하다 결국 솔루션을 참고해버렸다.

경로를 크게 3부분으로 나눈다.


가운데 지그재그로 제외할 1칸을 피해서 이동하는 것 때문이다. 지그재그로 2줄만 어떻게 해결하면 되므로, 그 전행까지는 R...RDL...LD 로 이동하고, 이후의 행부터는 DL...LDR...R 로 이동하면 된다.

#include <cstdio>
using namespace std;

int H, W;
int a[1000][1000];

void repeat(char ch, int repeated){while(repeated-->0)putchar(ch);}

int main(){
    scanf("%d %d", &H, &W);
    
    for(int i=0; i<H; ++i){
        for(int j=0; j<W; ++j){
            scanf("%d", &a[i][j]);
        }
    }
    
    if( H%2 ){
        repeat('R', W-1);
        for(int i=0; i<H-1; i+=2){
            repeat('D', 1); repeat('L', W-1);
            repeat('D', 1); repeat('R', W-1);
        }
    } else if( W%2 ){
        repeat('D', H-1);
        for(int i=0; i<W-1; i+=2){
            repeat('R', 1); repeat('U', H-1);
            repeat('R', 1); repeat('D', H-1);
        }
    } else {
        // finding minimum cell
        int x=0, y=1;
        for(int i=0; i<H; ++i){
            for(int j=0; j<W; ++j){
                if((i+j)%2 && a[y][x] > a[i][j]){
                    y = i, x = j;
                }
            }
        }
    
        int h=0, w=0;
        // top
        for(; h+1 < y; h += 2){
            repeat('R', W-1); repeat('D', 1);
            repeat('L', W-1); repeat('D', 1);
        }
        
        // middle
        int ny = h^1;
        for(w=0; w < W-1; ++w){
            if( ny != y || w != x ){
                repeat( ny%2 ? 'D':'U', 1);
                ny ^= 1;
            }
            repeat('R', 1);
        }
        if( ny%2 ) repeat('D', 1);
        
        // bottom
        h = (h/2*2) + 2;
        for(; h < H; h += 2){
            repeat('D', 1); repeat('L', W-1);
            repeat('D', 1); repeat('R', W-1);
        }
    }
    
    return 0;
}

2016년 10월 26일 수요일

1541 - 잃어버린 괄호

https://www.acmicpc.net/problem/1541

음수 부호(-)가 나오는 순간부터 모든 수가 음수라고 생각하면 된다. a+b-(c+...+z) 가 되기 때문.

#include <bits/stdc++.h>
using namespace std;

int main() {
    char s[101];
    scanf("%s", s);
    int num=0, sum=0;
    bool sub = false;
    for(int i=0; s[i]; ++i){
        if('0' <= s[i] && s[i] <= '9'){
            num = 10*num + (s[i]-'0');
        } else {
            if(!sub) sum += num;
            else sum -= num;
            num = 0;
            sub |= s[i] == '-';
        }
    }
    if(!sub) sum += num;
    else sum -= num;
    
    printf("%d", sum);
    return 0;
}

1080 - 행렬

https://www.acmicpc.net/problem/1080

왼쪽에서 오른쪽으로, 위에서 아래로 한 칸씩 확인하면서 3x3크기의 부분행렬이 서로 다르다면 바꾼다. 마지막에 바꾼 횟수를 출력하면 정답이다.

이런 그리디가 통할 수 있는 것은, 이미 지나간 부분(탐색한 부분)에서 서로 다른 칸이 존재하면 안되기 때문이다.

#include <bits/stdc++.h>
using namespace std;

int h, w;
char A[50][51];

void flip(int y, int x){
    for(int i=y; i<y+3; ++i){
        for(int j=x; j<x+3; ++j){
            A[i][j] = !A[i][j];
        }
    }
}

int main(){
    scanf("%d %d", &h, &w);
    for(int i=0; i<h; ++i) scanf("%s ", A[i]);
    for(int i=0; i<h; ++i){
        char s[51];
        scanf("%s ", s);
        for(int j=0; j<w; ++j){
            A[i][j] = A[i][j] == s[j];
        }
    }
    
    int ans = 0;
    for(int i=0; i<h-2; ++i){
        for(int j=0; j<w-2; ++j){
            if( A[i][j] == 0 ){
                flip(i, j);
                ans += 1;
            }
        }
    }
    
    for(int i=0; i<h; ++i){
        for(int j=0; j<w; ++j)
            if(A[i][j] == 0) return puts("-1"), 0;
    }
    
    printf("%d\n", ans);
    
    return 0;
}

2016년 10월 23일 일요일

띠용 이게 무슨 일이지


띠용
무슨일인지 갑자기 트래픽이 평소를 훌쩍 넘긴 811을 기록했다.
최고기록이니 일단 글로 적어서 저장하자 (....)

레퍼러 보니까 구글에서 1509 - 팰린드롬 분할 이거 타고 온거 같은데 무슨일이지 정말..

맨날 구글 크롤러 봇따위만 오는 블로그인줄 알았는데.. 크흡ㅠ

다시 문제풀이를 열심히 하라는 계시로 알아들어야겠다.

2016년 10월 20일 목요일

5535 - 패셔니스타

https://www.acmicpc.net/problem/5535

각 날짜에 대해 최고기온을 고려해서 입을 수 있는 옷들을 정리한다.


각 날짜마다 한 옷을 선택한다고 하면, D일동안 N개의 옷으로는 경우의 수는 \(O(N^D)\) 이다.

dp 점화식을
dp[D][N] = D번째 날에 N번째 옷을 선택했을 때의 문제의 정답
이라고 하면 \(O(ND)\) 으로 줄일 수 있다.


#include <bits/stdc++.h>
using namespace std;

#define INF 987654321

typedef long long lld;

int D, N;
vector<int> a[201];

int dp[201][201];

int f(int pos, int sel){
     if(pos == D-1) return 0;
     
     int& r = dp[pos][sel];
     if(r != -1) return r;
     
     r = 0;
     for(int i=0; i < a[pos+1].size(); ++i){
      int diff = abs(a[pos][sel] - a[pos+1][i]);
      r = max(r, diff + f(pos+1, i));
     }
     return r;
}

int main(){
     memset(dp, -1, sizeof(dp));
     
     scanf("%d %d", &D, &N);
     int T[D];
     for(int i=0; i<D; ++i) scanf("%d", &T[i]);
     for(int i=0; i<N; ++i){
      int low, high, c;
      scanf("%d %d %d", &low, &high, &c);
      for(int j=0; j<D; ++j){
       if(low <= T[j] && T[j] <= high){
        a[j].push_back(c);
       }
      }
     }
     
     int r = 0;
     for(int i=0; i < a[0].size(); ++i){
      r = max(r, f(0, i));
     }
     printf("%d", r);
     
     
     return 0;
}

2016년 10월 13일 목요일

13325 - 이진 트리

https://www.acmicpc.net/problem/13325

2016 ACM-ICPC 한국 인터넷 예선 A번

각 노드에서 리프 노드까지의 거리가 (왼쪽으로 가든지, 오른쪽으로 가든지) 같도록 조정하는 것이므로 재귀를 생각할 수 있다.

이를 해결할 작은 문제로 재귀의 중간 과정부터 생각했다.

왼쪽과 오른쪽의 길이가 달라져서 조정이 필요한 상황은 "왼쪽 != 오른쪽" 이다. 이를 조정하는 작업은 더 작은 쪽에 가중치를 증가시키는 것이다.

가중치는 보정을 위해 추가하는 것이므로, 맞추고자 하는 차이만큼 증가시키면 된다.

#include <bits/stdc++.h>
using namespace std;

#define INF 987654321

typedef long long lld;

int n;
int a[(1<<21)+1];

int leftPos(int pos){ return 2*pos+1; }
int rightPos(int pos){ return leftPos(pos)+1; }

int f(int pos){
    int l = leftPos(pos), r = rightPos(pos);
    // is leaf
    if(l > n || r > n) return a[pos];
    
    int lsum = f(l), rsum = f(r);
    if(lsum < rsum){
        a[l] += rsum - lsum;
    }
    else if(lsum > rsum){
        a[r] += lsum - rsum;
    }
    
    return a[pos] + max(lsum, rsum);
}

int main(){
    int k;
    scanf("%d", &k);
    n = (1<<(k+1))-1-1;
    for(int i=1; i<=n; ++i) scanf("%d", &a[i]);
    f(0);
    int ans = 0;
    for(int i=1; i<=n; ++i) ans += a[i];
    printf("%d", ans);
    return 0;
}

2016년 9월 23일 금요일

Codeforces Round #372 (Div. 2)

http://codeforces.com/contest/716

A. Crazy Computer

수 사이의 차이가 C 이하이면 카운팅하고, 그렇지 않으면 카운트가 0으로 초기화된다. 수열 끝에서 카운트가 몇인지 출력한다.

#include <bits/stdc++.h>
using namespace std;

#define INF 987654321

typedef long long lld;

int main(){
    int n, c, a[1000001], len=0;
    scanf("%d %d", &n, &c);
    for(int i=0; i<n; ++i){
       scanf("%d", &a[i]);
       if(i > 0){
          if(a[i]-a[i-1] <= c) len += 1;
          else len = 1;
       } else len = 1;
    }
    printf("%d", len);
    return 0;
}

B. Complete the Word

본문에서 "... to replace all the question marks with uppercase letters such that ..." 이라고 했는 데, 강조된 uppercase letters만 보느라 "replace all the question marks"를 놓쳤다. 물음표를 계속 그대로 방치해서 결국 계속 틀렸던 문제.

본문의 조건을 좀 더 세심하게 읽자.

틀린거 고치느라 급해서 $O(50000 * 26)$ 으로 제출

#include <bits/stdc++.h>
using namespace std;

#define INF 987654321

typedef long long lld;

int main(){
   char s[50501]={};
   scanf("%s ", s);
   bool ans = false;
   for(int i=0; s[i]; ++i){
      bool used[26]={};
      char f[26]={};
      int j, c=0;
      for(j=0; j<26 && s[i+j]; ++j){
         if(s[i+j] == '?') continue;
         if(used[s[i+j]-'A']) break;
         used[s[i+j]-'A'] = true;
      }
      if(j == 26) ans = true;
      if(ans){
         for(j=0; j<26; ++j){
            if(!used[j]) f[c++] = 'A'+j;
         }
         int c2=0;
         for(j=0; j<26; ++j){
            if(s[i+j] == '?') s[i+j] = f[c2++];
         }
         break;
      }
   }
   if(!ans) return puts("-1"), 0;
   for(int i=0; s[i]; ++i) putchar(s[i] == '?' ? 'A' : s[i]);

   return 0;
}

2016년 9월 12일 월요일

문제적남자 73화:수학 - 코딩으로 풀어보기

문제적남자 73화 - 수학 풀이


수능 D-100 특집으로 이런 문제가 나왔다.

첫 번째 숫자까지는 1로 나누어지고,
두 번째 숫자까지는 2로, .... 열 번째 숫자까지는 10으로 나누어진다.
0부터 9까지 10개의 숫자를 모두 사용해 규칙에 맞는 수를 만들어라.

다음과 같은 몇 가지 규칙을 발견하고 브루트 포스로 풀어보기로 했다.

1. 열 번째 숫자는 0 이다. (10의 배수는 0으로 끝나기 때문)
2. 다섯 번째 숫자는 5 이다. (5의 배수는 0, 5로 끝난다. 0은 열 번째 숫자이므로 5)
3. 짝수 번째 숫자는 2, 4, 6, 8 중 하나이다.
4. 홀수 번째 숫자는 1, 2, 3, 4, 6, 7, 8, 9 중 하나이다.

소스: https://jsfiddle.net/J00nas/yrmen84L/

javascript로 적어봤는데, 비동기식이라 그런지 제대로 걸러지지가 않는다. (그리고 브라우저의 자바스크립트 버전별로 다르게 보일 수 있다.) 그래도 답은 나온다.


모던 브라우저에서는 위처럼 보일것이다.

자바스크립트로는 코드가 정확하지 않은 것 같아서 C++로 다시 적었다.
소스: https://ideone.com/yoLJXx

신기하게도 규칙을 만족하는 숫자는 3816547290 뿐이었다.

2016년 9월 10일 토요일

2991 - 사나운 개

https://www.acmicpc.net/problem/2991

$p$분 후에 우체부가 도착한다면, $p \% (a+b)$ 가 $a$(공격시간)보다 이후라면 공격받지 않는다.

#include <cstdio>

bool attacked(int n, int a, int b){
    n %= (a+b);
    return n < a;
}

int main(){
    int a, b, c, d, n;
    scanf("%d %d %d %d", &a, &b, &c, &d);
    for(int i=0; i<3; ++i){
        scanf("%d", &n);
        printf("%d\n", attacked(n-1, a, b) + attacked(n-1, c, d));
    }
    return 0;
}

2016년 9월 9일 금요일

2118 - 두 개의 탑

https://www.acmicpc.net/problem/2118

문제를 푸는 데 가장 크게 작용한 아이디어는
문제의 정답 $\le$ 전체 길이의 합 / 2
라는 사실이었다.

두 지점 사이의 거리는 "$x$ = [a, b) 사이의 부분 합" 라고 할 때 $x$ 와 전체 길이의 합 - $x$ 중 더 작은 것이 된다.
이러한 부분합들 중 가장 큰 것이 문제의 정답이다.

부분합을 구하려는 데 계속 $O(N^2)$ 밖에 안 떠올랐다. 시계방향과 반시계방향 두 경로 중 더 작은것을 선택하는 것 때문이었다.
"문제의 정답 $\le$ 전체 길이의 합 / 2" 이니까 전체 길이 합의 절반보다 작지만 가장 큰 수만 찾으면 되겠구나 생각하고 이진탐색을 궁리해보았다.

계산하고 하는 거리 [a, b) 는 a $\le$ b인 것들만 확인해도 충분했다. 예를 들어, [5, 3) 을 확인하고 싶다면 전체 거리의 합에서 [3, 4) 를 빼면 되기 때문이다. (한쪽 방향에 대해서만 본다면)

시계방향/반시계방향에 대해 위 계산을 범위를 좁혀가면서 했다.

2016년 8월 31일 수요일

라이선스 알아보기

github repository를 생성하다가 Licenses를 선택하면서 멍 때렸다.



이제껏 MIT License로 만들거나 없는 상태로 생성했는데 하나씩 뭔지 알아보기로 했다.

OLIS(Open Source Software License Information System)에 라이선스 비교표가 있다. 70종 이상의 라이선스에 대해 자세히 나와있다.

아파치, GNU(GPLv3), MIT 이렇게 3가지만 요약해볼까한다. 비교표에 따르면 아래와 같다.
[출처: https://www.olis.or.kr/ossw/license/compareGuide.do]

GNU GPLv3
 - 강도가 센 편
 - 반드시 전체 소스 코드를 무료로 공개해야한다.
 - 공유와 수정의 제한이 목적이 아니라 자유 보장이 목적

아파치 2.0
 - 파생 프로그램을 누구나 제작할 수 있고, 저작권을 양도, 전송할 수 있다.
 - 개인적 목적, 상업적 목적으로 이용 가능하고 재배포시에 원본의 소스코드 역시 포함될 필요가 없다.
 - 아파치 라이선스에 대해서는 명확히 밝혀야한다.

MIT 라이선스
 - 비교적 느슨하다.
 - 반드시 오픈소스일 필요는 없다.
 - 재사용을 인정한다.
 - 2차 저작물에 대해 추가 조항을 덧붙일수가 있다.

2016년 8월 23일 화요일

1715 - 카드 정렬하기

https://www.acmicpc.net/problem/1715

항상 카드의 장수가 가장 작은 두 묶음을 합치는 것이 결과적으로 최소의 비교 횟수이다.
대체 왜..?
라고 생각할수있다. "왠지 그러는 게 맞는 것 같다"는 말도 안되는 논리(혹은 풀이 도출)로 풀어온 문제가 정말 많았다.
그래서 오늘은 증명을 연습해보려 한다.

카드를 합치는 과정을 설명하기 위해 다음과 같이 정의해보겠다.
$a =$ 가장 작은 수 (i.e. $a$장의 카드 묶음)
$b =$ 두번째로 작은 수
$c = a, b$ 가 아닌 다른 어떤 수

위 정의에 의하면 $a \le b \le c$ 이다. 아래 3가지 경우가 있다.

  • $a$와 $c$를 합친 후, 그것을 $b$와 합친다 : $(a+c)+ \{(a+c)+b\}$
  • $a$와 $b$를 합친 후, 그것을 $c$와 합친다 : $(a+b)+ \{(a+b)+c\}$
  • $b$와 $c$를 합친 후, 그것을 $a$와 합친다 : $(b+c)+ \{(b+c)+a\}$

세 항 모두 중괄호로 둘러싼 부분이 $a+b+c$로 공통이므로 비교하기 위해 생략할 수 있다.

$a+b \le a+c$ 이고, $a+b \le b+c$ 이므로
$(a+b)+ \{(a+b)+c\}$ 가 가장 최소이다.

따라서 가장 작은 두 수를 합치는 것이 항상 최소이다.

증명을 별로 해본적이 없어서 맞는 지 확신이 안 선다.
댓글로 지적해주시면 감사하겠습니다.

2016년 8월 18일 목요일

구글 드라이브 호스팅 서비스 종료

구글에서 메일이 왔다. 메일 내용은 다음과 같았다.

On Aug 31, 2016, we will discontinue serving content via googledrive.com/host/[id] and the webpages will not be accessible anymore.
As an alternative to web hosting in Drive, we recommend:
  • Blogger—An easy and free way to host websites.
  • Firebase Hosting— An alternative if you’re using the web-hosting feature to serve static webpages with items on Drive.

구글 드라이브를 통해서 호스팅 서비스를 지원하고 있었는 데, 16년 8월 31일부터 더 이상 지원하지 않는다는 내용이다.

지금 이 블로그도 코드 문법 하이라이팅(SyntaxHighlighter) 파일을 구글 드라이브 호스팅으로 가져오고 있는데, 적당한 파일호스팅을 찾아봐야겠다..

2016년 8월 2일 화요일

[Node.js] TypeError: require(...) is not a function 해결방법

'use strict';

module.exports = function(param){
    // something to handle
    require('./otherModule)(param);
});

위와 같은 코드 형태에서 TypeError: require(...) is not a function 에러가 났었다.
아래처럼 고치니 잘 작동했다.

module.exports = function(param){
    // something to handle
    require('./otherModule)(param);
});

혹시나싶어서 'use strict'를 지웠는데, 그것이 먹혔다.
이유를 알고싶어 검색하다가 아래와 같은 글을 찾았다.
Aliencube Community :: 자바스크립트에서 strict mode를 사용해야 하는 이유

엄격한 컨텍스트에서는 require('...')() 을 제한하나보다. 혹은 "상대적으로 안전하지 않은 액션"이였거나..


2016년 7월 25일 월요일

req.user is undefined on node + express + passport

Node.js + Express 4.x + passport 환경에서 로그인(또는 사용자 인증) 후에 Request 객체에 user가 저장되지 않아요.

express와 passport 모두 잘 동작하지만 사용자 인증 후에 req.user를 확인하면 아무값도 없는 현상이 나타났다.

express를 설정하는 부분에서 미들웨어 등록 순서때문에 일어난 일이다.

// set session
app.use(session({
    secret: 'SomethingPowerfulSecret'
}));
// set passport
app.use(passport.initialize());
app.use(passport.session());

express에 session 미들웨어를 설정한 후에, passport를 등록해야 정상적으로 동작한다.

2016년 6월 21일 화요일

어두운 방(A Dark Room) 클리어

http://adarkroom.doublespeakgames.com/?lang=ko

클리어까지 한 5시간 반 정도 걸린 거 같다.

흙길(황무지)를 돌아다니다 보면 우주선을 찾는 데, (의심할 필요도 없이) 이걸 타고 탈출하면 게임이 끝난다. 스토리도 없고 약간 허무해서 나무위키를 봤더니 스토리가 있긴 있었다. 스포주의
근데 나무위키에서 설명하는 변화는 내 플레이에서는 없었다.


마지막 결과는 이렇지만, 사실 유황은 딱히 필요가 없다.
유황은 총알의 재료인데 총알은 흙길에서 군인을 죽이면 얻을 수 있기 때문이다.
(총알을 보면 108개씩이나 남았는데, 난 총알을 만든 기억이 없다.)
즉, 무기고를 만들 필요가 없었다.

강철을 만드는 데 시간이 좀 걸렸다.

사냥꾼과 훈연꾼의 밸런스를 적절히 유지하면 무한 동력이 가능하다.
물론, 오두막을 꾸준히 늘려줘야한다. (계속 불타서 없어짐..)

"특기:권투 선수"는 모르고 뼈 창을 안 들고 몇번 나갔더니 주먹이 쎄졌다 (....)
"특기:잠입"은 도둑을 잡았길래 죽인다/풀어준다 에서 풀어준다고 했더니 내게 "은신술"을 알려줬다. 효과는 "야외에서는 싸우지 않는 편이 더 낫다" 라는 데.. 쓸모없다.
"특기:미식가"는 어쩌다 땄는 지 기억이 안난다. 전투 시 고기를 먹으면 체력이 조금 더 많이 찬다.

클리어하기 전 찍은 지도
왼쪽 아래에 아무것도 없어서 물탱크를 들고가도 가기가 힘들다 (....)
전초기지(P)는 황폐한 도시나 옛 전쟁터를 점령하면 물 충전+소량의 훈제고기를 얻는 곳으로 쓰인다.
물이 1칸에 1씩 달아서 움직일때마다 최단거리를 계산한 적이 몇번 있었다. (택시기하학이 이렇게 쓰일 줄이야...)

우주선을 찾자마자 "어 이거 대놓고 엔딩보는 아이템이네" 라고 하면서 찍었다.

마지막 세이브 데이터

2016년 6월 1일 수요일

2016 IUPC 풀이

2016 IUPC 인하대학교 프로그래밍 경진대회

A. CTP공국으로 이민 가자
단순 구현

B. 상품 is 뭔들
$\sqrt(b) - \sqrt(a)$

C. 원피스
문자열이 겹쳐서 존재하는 것도 "등장한다"인지 설명이 애매해서 몇번 틀렸다. 문자열 H에서 문자열 N이 겹치지 않게 존재하는 개수를 출력한다. 문자열 N을 찾을때마다 문자열 N의 길이만큼 건너뛰면서 개수를 센다.

D. PIZZA ALVOLOC
두 직선의 교점이 존재하는 지를 구한다.

E. 비트 우정지수
어떤 한 수에서 비트가 다른 0의 개수를 $a$, 1의 개수를 $b$라고 했을 때,
$k=min(a,b)$ 라면 $k+max(a-k, b-k)$ 가 정답이다.

F. 곱셈 게임

G. 인하니카 공화국

H. 토쟁이의 등굣길
집-토스트 가게, 토스트 가게-학교 를 차례대로 경로의 개수를 누적하면서 구한다.

I. INHA SUIT
경우의 수가 많지 않아서 그냥 queue를 사용해도 충분하다. 각 상태 분기마다 5가지 경우(O, A, B, C, T)를 모두 탐색한다. 방문 체크는 visit(나무의 위치, T기능 사용 횟수) 로 구분한다.

J. 지금 밥이 문제냐
scanf의 형식 지정자로 . 마다 끊어서 long long 변수에 저장한 후 256진수처럼 출력한다.

K. 제 2회 IUPC는 잘 개최될 수 있을까?
내림차순으로 정렬
가장 많이 가지고 있는 사람부터 빌려본다. 모두 빌려도 돈이 부족하면 STRESS 아니라면 빌린 사람의 수가 정답

L. 도키도키 간식드리미
스택 문제

2016년 5월 30일 월요일

제곱근 구하는 알고리즘 비교

Best Square Root Method - Algorithm - Function (Precision VS Speed)

sqrt를 구하는 14개의 알고리즘을 비교한 글

sqrt(n)을 O($\sqrt{n}$)보다 빠르게 구할 수 없나를 알아보다 찾은 글

답은 못 얻었다.

결론은 std::sqrt 쓰는 게 빠르다.

2016년 5월 27일 금요일

LaTeX Usage (MathJax)

LaTeX 사용 문법

http://meta.math.stackexchange.com/..../mathjax-basic-tutorial-and-quick-reference

11058 - 크리보드

https://www.acmicpc.net/problem/11058

출력 결과에 영향을 미치는 연산이 A를 그냥 누르는 거랑(+1), Ctrl-V (+복사했던 크기) 인데 클립보드에 복사해놓은 크기때문에 재귀로 짜는데 애를 먹었다.

복사한 크기만큼 늘어나기 때문에, Ctrl-V 를 하기 위해서는 이전에 Ctrl-A, Ctrl-C 가 꼭 필요하다.

문제에 적힌 연산을 순서대로 A, S, C, V 라고 한다면 $N=6$인 경우는 아래와 같이 가능하다.
AAAAAA
AAASCV
AASCVV
ASCVVV
이 정도가 의미있는 타이핑인거같다. 타이핑을 n번한 것을 $f(n)$이라 하자. 그럼 위 4줄은 각각 $f(5)+1$, $f(3)*2$, $f(2)*3$, $f(1)*4$ 이다.

$$ f(0) = 0, f(n) =
\begin{cases}
f(n-1) + 1, \\
f(k) * (n-k-1), & \text{if $k$ < n - 4 }
\end{cases}
$$

알고스팟 - ROUTING

https://algospot.com/judge/problem/read/ROUTING

노이즈의 증폭이 최소가 되는 경로를 찾는 문제이다. 책에는 "노이즈 $c$ 는 언제나 1 이상 3 이하의 실수입니다" 라고 나와있다고 한다.

음.. 이게 딱히 문제는 없을 거 같지만, $c$의 범위를 몰라서 정답의 범위를 모르기때문에, 무한대값(INF)를 설정을 못한다. INF=987654321 로 설정하면 오답이 나온다.
987654321 보다 큰 경로만 남아있을 때, 방문하지 않은 정점들은 987654321 일테니 탐색하지 않아버린다.

근데 모든 노이즈가 3이면 어떻게 될까. 3^10000 ...!!

방문한 노드와 방문하지 않은 노드를 따로 구분해야한다. 다익스트라에서는 음수인 가중치가 없을테니 방문하지 않은 노드를 -1 로 해도 될거같다.

2016년 5월 26일 목요일

SyntaxHighlighter 변경

블로거에 설정한 Syntax Highlighter가 갑자기 적용이 안됬다.
무슨일이지 싶어서 콘솔을 봤더니 불러오기 실패라고 한다. (SOF 글 - Failed to load resources when trying to perform syntax highlighting in a blog) 을 보니까, 전에 연결한 프로젝트가 없어졌다고 한다 (예...??!)

Syntax Highlighter를 배포하는 제작자의 링크를 직접 연결하면 되지만.. 문제는 하이라이트를 적용하는 구문이 다르다 (!)

<pre name="code" class="언어"> 로 사용하고 있었는데, 이제 원래 구문인 <pre class="brush: 언어"> 로 써야한다. 이제까지 적었던 글들을 모두 바꿔야 하는가.... 허어......

우선 원저작자걸로 다시 연결하려했으나 link랑 script 태그에 적은 http 가 https 로 강제 변환됐다. 이유는 모르겠으나 http://itsonlycode.blogspot.com/2015/06/blogger-setup-syntaxhighlighter-for.html 를 참고하여 새로 설정했다.

지금 포스트 이후로는 class="brush:언어;" 형태로 쓰겠지만, 이전의 포스트들의 HTML 태그를 수정하지 않고 그대로 사용하고 싶다.

그래서 아래와 같은 코드를 추가했다.
<script type='text/javascript'>
    function addBrushToClass(){
        var codes = document.getElementsByName("code");
        for(var i in codes){
            var originClass = codes[i].className;
            codes[i].className = "brush: " + originClass +";";
            //codes[i].setAttributes('name', 'converted');
        }
    }
    window.setTimeout(function() {
        addBrushToClass();
        SyntaxHighlighter.config.bloggerMode = true;
        SyntaxHighlighter.all();
    }, 50);
</script>

2016년 5월 25일 수요일

1766 - 문제집

https://www.acmicpc.net/problem/1766

처음에는 문제의 접근 방향을 후위순회처럼 생각했다. 어떤 x번째를 하기 위해서 그 이전것을 무조건 해결해야하는 방식.
틀리고 나서 문제를 다시 이해했는데, 깨달은 테스트 케이스부터 말하자면 아래와 같다.
5 3
4 1
2 3
5 3
내가 생각한 이 입력의 정답은
2 4 1 5 3
이다.

1번보다 4번을 먼저 풀어야하고, 3번보다 2, 5번을 먼저 풀어야한다. 1번을 풀기 위해서 4번을 풀어야하는데, 같은 우선순위에서 4번보다는 2번이 쉽다. (둘 다 1개의 문제 앞에 있음)
2번을 풀고 4번을 풀면, 1번이 풀리고 남은 5번을 풀면 3번이 풀린다.

처음에 생각한 대로라면 4 1 2 5 3 이 나와야하는데, 이게 틀려서 위 생각으로 다시 풀었더니 맞았다.
위상정렬 도중에 indegree가 없는 정점을 선택할때마다 번호가 작은(문제 난이도가 낮은) 정점을 선택하게 하면 된다.

2016년 5월 22일 일요일

2016 JNUPC을 마무리하며

제 11회 전북대학교 프로그래밍 경진대회를 마치며..

올해에도 문제 출제를 맡았다. 물량으로 공세하다보니 10문제 중 4문제나 만들었다. 난이도 때문에 못낸 아쉬운 문제들이 많다.

특히 이번 대회는 강민이와 승균의 도움으로 대회 규모를 학교 전체로 넓히고, 8문제에서 10문제로 문제 수를 늘릴 수 있었다. BOJ에도 문제를 등록했다!

알고리즘이 별로 필요없는 5문제와, 알고리즘이 섞인 5문제로 출제했다. 알고리즘이 섞인 문제들 중 2~3문제는 꽤 정석적인 문제(BFS, LIS 등)를 냈다고 생각했는데, 문제 난이도에 비해 전체적인 solved 수는 낮았다. 내년 대회는 난이도를 어떻게 조정해야할지....

대회 당시에는 학부생들의 실력을 고려해서 일부 문제들은 Naive한 솔루션도 정답으로 처리하게 문제 조건과 데이터를 약간 느슨하게 했다. 하지만 I번 문제는 데이터 부실때문에 의도하지 않은 솔루션으로 풀려버렸다. 대회 순위 결과에는 영향이 없었지만 만약.... 윽. 내년에는 데이터를 더욱 꼼꼼하게!

ICPC 스타일의 스코어보드를 만들었지만 스크립트로 30초 단위 새로고침을 시켰고, 프리징도 없어서 이 기능들을 모두 완성하지 못함에 개인적으로 아쉬웠다.
더욱이 아쉬웠던 것은, 대회 내내 스코어보드가 재밌었다! 대회 종료 10초전에 ACCEPT를 받은 팀부터, 1, 2등의 순위싸움과 3~5등의 순위싸움. 3위 팀의 굳히기와 6위 팀의 역전 등..

내년에는 프리징과 애니메이션을 꼭 추가해야겠다.

대회 홈페이지 호스팅도 엉망이고, 온라인 저지 역시 혼돈의 카오스다. 개선해야할 문제점들이 너무 많다..

전대 프로그래밍 대회 타이틀때문에 계속 포스터를 바꾸고 (.....) 고생했지만, 이번 행사도 잘 마무리했다.
내년 포스터는 올해꺼 숫자만 바꿔서 써야지

이번 대회로 고생한 강민이와 승균이의 노고에 심심한 감사를 표한다.

2016년 5월 18일 수요일

Codeforces Round #353 (Div. 2)

http://codeforces.com/contest/675

A. Infinite Sequence

$a$에서 $c$의 차이만큼 계속 더해서 $b$를 만들 수 있는가를 묻는 문제이다.
직접 돌리는건 안된다. 예를 들어, $c=0$ 이기만해도 반복문으로는 안된다.

$ a+k*c == b $ 가 되는 $k$가 존재하는 가? 다시말해, $(b-a) \mod c == 0$ 인가? 이다.
#include <bits/stdc++.h>
using namespace std;

int main(){
        int a, b, c;
        while(~scanf("%d %d %d", &a, &b, &c)){
        if(c == 0) puts( a == b ? "YES" : "NO" );
        else if(c > 0 && b < a) puts("NO");
        else if(c < 0 && b > a) puts("NO");
        else puts( (b-a)%c == 0 ? "YES":"NO" );
    }
    
    return 0;

}

B. Restoring Painting

2x2크기로 묶었을때 나오는 사각형 안의 수들의 합이 모두 같아지게 하는 빈칸의 경우의 수를 묻는 문제였다. 문제의 핵심은 정가운데 $?$는 전체 합에 영향을 미치지 않는다는 것이다.
그럼 나머지 모서리에 있는 4개의 $?$ 중에 하나를 설정해보면 나머지 물음표가 모두 해결되서, 이게 가능한 수인지 판별이 가능하다.
한 모서리 칸을 1~$N$까지 하나씩 설정하면서 가능한 경우를 세면 된다.

근데 코딩이 말려서 계속 틀렸다. 흠.. 분발해야겠다.

2016년 5월 11일 수요일

2082 - 시계

https://www.acmicpc.net/problem/2082

각 자리에서 가능한 후보들을 추린다.
어떤 숫자가 나타나려면 다음과 같은 조건을 만족해야한다.
  • 시계에 불이 들어와있다면, 수 $X$도 그 자리가 켜져있어야한다.
  • 수 $X$에 불이 꺼져있는 자리는, 시계에도 불이 꺼져있어야한다.
  • 시계에 불이 꺼져있어도, 수 $X$에 불이 들어온것과는 상관이 없다.

1089 - 엘리베이터

https://www.acmicpc.net/problem/1089

2082번: 시계같은 구현문제일 줄 알고 가벼운 마음으로 시작했다.
풀다보니 모든 수의 합을 구하는 과정에서 DP로 작성하고 있었다. (하지만 DP로는 풀지 못했다.)

우선 각 자리에서 나올 수 있는 숫자들을 추려냈고, (이 과정은 2082번 풀이를 참고)
만들어진 수에 이전에 나온 경우의 수만큼 곱한다. 그만큼 중복으로 등장하기 때문이다.

각 자리에서 나올 수 있는 수를 괄호로 묶었을 때, $(0, 8)$, $(8, 9)$ (예제)와 같은 경우는 1의 자리에서 $8, 9$가 두번씩 나온다. 자리수가 길수록, 다른 자리에 가능한 수가 다양할수록 등장 횟수는 많아질 것이다. 그래서 DP를 생각했다.
가능한 숫자들의 을 구하는 DP 코드는 아래와 같다.

typedef long long lld;

int n;
vector<int> cand[10]; // i번째 자리에 가능한 숫자들
int nCase[11];
lld DEC[11]={1,1,10,100,1e4,1e5,1e6,1e7,1e8,1e9};

lld dp[11];
lld sum(int pos){
    if(pos >= n) return 0;
    
    lld& r = dp[pos];
    if(r != -1) return r;
    
    r = 0;
    for(int i=0; i<cand[pos].size(); ++i){
        r += nCase[pos+1] * cand[pos][i] * DEC[n-pos] + sum(pos+1);
    }
    return r;
}

뭔가 찝찝한 느낌이 있었는데 채점 40% 정도에서 틀렸다. 오버플로우가 아닐까 싶었다.

아래 입력은 각 자리마다 $(2, 8), (0, 2, 8), (3, 8, 9)$ 의 조합으로 수가 만들어질 수 있다.
3
###.###.###
..#...#...#
.#......##.
#...#.....#
###.###....
위 입력의 정답은 $540.0$ 이다.

가능한 수를 모두 적어보면 아래와 같다.
203
208
209
223
228
229
283
288
289
803
808
809
823
828
829
883
888
889

1의 자리에서 (3+8+9)가 6번 나온다. 10의 자리에서 (0+20+80)이 2번*3번 나온다.
이런식으로 수를 쪼개보면 $10^x$의 자리에서 나올 수 있는 수들의 합은 (자리에서 가능한 숫자들의 합) $\ast$ (전체 경우의 수 / 가능한 숫자의 개수) 이다.

지금 이걸 하려는 이유가 합을 모두 누적한다면 오버플로우가 나기 때문이라고 생각해서이다.
평균값은 (전체의 합)$/$개수이므로 어떤 수 하나의 비율은 (수 하나)$/$개수 이다. 각 숫자들의 비율을 모두 더해도 평균은 똑같이 구할 수 있다!

비율로 계산한다면 (자리에서 가능한 숫자들의 합) $\ast$ (전체 경우의 수 / 가능한 숫자의 개수) / (전체 경우의 수) 를 누적하면 되는 데 약분하면, (자리에서 가능한 숫자들의 합) / (가능한 숫자의 개수) 이다.

풀고 난 후

long long으로 해도 오버플로우는 안 난다.
lld DEC[11]={1,1,10,100,1e3,1e4,1e5,1e6,1e7,1e8,1e9};
배열에서 1e3를 빼먹어서 틀린거였다..

2016년 5월 10일 화요일

1477 - 휴게소 세우기

https://www.acmicpc.net/problem/1477

정답을 미리 가정하고 정답의 범위를 점차 줄여나가보자.
만약 정답(휴게소가 없는 최대 구간)이 $x$라면 몇 개의 휴게소를 지어야할까?

길이가 $L$인 도로에는 $\{a_0, a_1, \dots, a_{n-1}\}$ 과 같이 휴게소가 있다고 하자.
최대 구간의 길이가 $x$라면 $a_0$과 $a_1$ 사이에는 $\frac{a_1-a_0-1}{x}$개 만큼의 휴게소가 필요하다.

$x$의 범위를 점차 줄여나간다면 휴게소의 개수($C_x$)가 원하는 $M$개 이상인 순간이 문제의 정답이다.

휴게소의 개수 $C_x$가 너무 많다면, 휴게소가 너무 많이 지어졌다는 의미이다. 너무 많이 지은 이유는 구간의 길이 $x$가 너무 좁았기 때문이다. $x$ 범위의 하한을 높이면 된다.
너무 적은 경우에는 반대로 $x$ 범위의 상한을 낮춘다.

도로의 처음과 끝 사이에도 휴게소를 세울 수 있으니 위치가 $0$인 곳과 위치가 $L$인 곳에도 가상의 휴게소가 있다고 생각하자.

9437 - 사라진 페이지 찾기

https://www.acmicpc.net/problem/9437

음.. 페이지를 직접 인쇄하듯이 적어본 다음에 주어진 페이지가 있는 쪽을 직접 확인했다(!)

2865 - 나는 위대한 슈퍼스타K

https://www.acmicpc.net/problem/2865

본선에 $K$명만 나갈수 있다. 한 사람이 여러 장르를 부를 수는 없지만, 여러 사람이 같은 장르를 부를 수는 있다.

굵은 글씨가 문제의 핵심인 것 같다. 각 참가자의 제일 높은 점수를 더하면 된다.
$K$명만 선발해야하므로, 최고점수가 높은 순으로 $K$명을 고른다.

7808 - Email from The Professor

https://www.acmicpc.net/problem/7808

문자열을 n개만큼 한 줄씩 잘라서 각 칸마다 대응되는 key를 새로운 줄로 삼는 문제이다.
vector로 각 문자를 대응되는 줄(key)에 차곡차곡 붙였다.
마지막엔 각 줄을 일렬로 다시 출력했다.

10465 - Rolling Encryption

https://www.acmicpc.net/problem/10465

이전 $k$의 길이만큼에서 가장 많이 출현한 알파벳만큼 shift한다. 오른쪽으로 이동하면서 이전 길이 $k$만큼의 빈도를 계속 갱신했다. $k \le 10,000$와 $c \le 100,000$가 생각보다 커서 라인 스위핑하듯이 했다. 이걸 스위핑이라고 해도 될지 모르겠다.

1700 - 멀티탭 스케줄링

https://www.acmicpc.net/problem/1700

멀티탭에 k개 이상 꽂아야하는 순간 하나를 뽑는다. 뽑아야할 녀석은 가장 나중에 사용되는 플러그부터 뽑는다. 그런게 없다면 아무거나 뽑는다.

5015 - ls

알고스팟의 'WILDCARD' 문제에서 ? 가 패턴에 없는 문제이다. (하위호환...)
내용은 링크로 대체한다.

함께보기

2016년 5월 7일 토요일

12014 - 주식

https://www.acmicpc.net/problem/12014
국정원 소프트웨어 SW 공채 역량평가 샘플 예제
(여기 참조)

주어진 배열의 LIS의 길이가 $K$ 이상이면 1, 아니면 0을 출력하면 된다.

$N$의 범위로 보아 시간복잡도가 적당한 LIS를 사용한다. 길이만 알면 되서 $O(NlogN)$으로 작성했다.

2016년 5월 6일 금요일

알고스팟 - QUANTIZE

https://algospot.com/judge/problem/read/QUANTIZE

사용할 숫자의 개수를 "그룹의 개수"라 생각했다. 한 그룹에서 오차의 합이 최소가 되려면 그룹의 평균을 기준(양자화하는 숫자)으로 잡아야한다.

모듈화를 제대로 못해서 코딩이 계속 꼬였다. 다음부터는 함수로 적절히 쪼개서 문제를 푸는 데 방해되지 않도록 해야겠다.


2016년 5월 3일 화요일

알고스팟 - WILDCARD

https://algospot.com/judge/problem/read/WILDCARD

패턴 문자열 $p$와 비교 대상 $s$을 한자리씩 비교한다. 이 때, 비교하는 위치는 서로 다를 수 있다.
비교하고자하는 $p$의 위치를 $i$, $s$의 위치를 $j$ 라 하면, 각 자리의 비교 함수 $f(i, j)$의 진행은 아래와 같이 조건 분기가 가능하다.

- $p_i$와 $s_j$가 같거나 $p_i$가 ?인 경우, $f(i+1, j+1)$ 을 확인한다.
- $p_i$가 *인 경우, *가 $[s_j, s_k]$ ($k$는 문자열 $s$의 끝 위치) 까지의 범위를 대응시킬 수 있다. 하나씩 확인해본다.
- $p_i$와 $s_j$가 다른 경우, 패턴을 찾을 수 없다.
- 종료조건 : 패턴에 있는 모든 문자에 대해 검색이 끝났을 때, 비교 문자열 역시 검색을 마친(끝 위치에 있는) 경우라면 패턴에 부합한다는 뜻이다.

2016년 4월 25일 월요일

11966 - 2의 제곱인가?

https://www.acmicpc.net/problem/11966

2의 제곱은 2로 한번만 나누어 떨어진다.

6362 - Unimodal Palindromic Decompositions

https://www.acmicpc.net/problem/6362

$N=7$일 때 아래와 같이 표현할 수 있다.
[그림 1]
가운데 숫자를 회색숫자(1,2,3..)만큼 쪼개서 양쪽에 붙인다고 생각했다. [그림 1]은 해석하면 [그림 2]와 같은 뜻이다.
[그림 2]
하지만 문제의 정의상 가운데에서 끝으로 갈수록 숫자가 (같거나) 작아야하므로 (3 1 3) 이나 (1 2 1 2 1) 같은 수열은 정답으로 셀 수 없다. 따라서 쪼개는 수(회색숫자)를 $k$라고 했을 때, $k \le n-2k$ 인 경우에만 쪼갤 수 있다. 
$k$는 쪼개서 옆에 붙인 숫자이다. 이 말은 가장 끝에 $k$ 보다 큰 수는 없다는 것이다. 항상 $k=1$ 가 아닌 $k=$이전의 $k_{prev}$ 부터 시작해야 한다.

짝수의 경우 역시 위와 동일하게 진행하되, (8) -> (4 4) 와 같은 경우가 가능하기 때문에 $k \le \frac{n}{2}$ 인지를 더해준다.

참고로 $N=8$ 일 때 (1 2 2 2 1) 에서 (1 2 1 1 2 1) 을 안 세도록 해야한다.

2016년 4월 24일 일요일

C++ Code Template

문제풀이를 위한 C++ 코드 템플릿
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <sstream>
#include <algorithm>
#include <functional>
#include <limits>
#include <complex>
#include <stack>
#include <queue>
#include <deque>
#include <string>
#include <vector>
#include <map>
#include <set>
#include <list>
using namespaces std;

#define INF 987654321

typedef long long lld;
typedef pair<int, int> ii;

int main(){
   
    return 0;
}

채점 서버의 사용 컴파일러가 GCC 4.8.0 이상 버전이라면 #include <bits/stdc++.h>로 대체 가능하다.

2016년 4월 23일 토요일

2848 - 고창어

https://www.acmicpc.net/problem/2848

그냥 쭉 쓰다보니 코드가 너무 길어졌다.
새로 정의한 사전순 문자열로부터 문자의 순서를 재정의한다.
예제 같은 경우는 l->u, l->k, u->k, k->a 이렇게 4개

1. 올바른 순서가 없는 경우
 : 모순이 존재한다는 것. 싸이클이 있다는 것이다.
 : 플로이드 돌린 후에 다시 자신으로 돌아오는 경로가 있으면 싸이클임.

2. 가능한 순서가 여러개인 경우
 : 사전의 첫 글자가 여러개이거나, (첫 글자로부터) k번째 숫자에 올 수 있는 수가 여러개인 경우.

위에 적은 것만 구현했는데 코드가 1.6KB나 된다. 소스 정말 더럽다.

2016년 4월 22일 금요일

11340 - Making the Grade?

https://www.acmicpc.net/problem/11340

$x$를 기말 점수, 나머지를 $a, b, c$라 하면 다음을 만족하는 $x$ $$ 40x \ge \frac{9000}{15a+20b+25c} $$
#include <bits/stdc++.h>
using namespace std;

int main(){
    int T;
    scanf("%d", &T);
    for(int i=0; i<T; ++i){
        int a, b, c;
        scanf("%d %d %d", &a, &b, &c);
        int r = ceil((1800-3*a-4*b-5*c)/8.);
        if( r > 100 ) puts("impossible");
        else printf("%d\n", r);
    }
    return 0;
}

1727 - 커플 만들기

https://www.acmicpc.net/problem/1727

비교하고자 하는 배열 $a$의 크기는 항상 배열 $b$보다 작다.
배열 $a$로부터 차이를 최소로 하는 배열 $\{ b_i, b_{i+1}, \dots, b_j \}$ $(0 \le i \le j \le n)$ 을 찾는다.

스위핑으로 되려나싶어서 코드를 다음과 같이 작성해봤다.
int ptr = -1, sum = 0;
for(int i=0; i<n; ++i){
    int val = INF;
    for(int j=ptr+1; j<=m-(n-i); ++j){
        if(val > abs(a[i]-b[j])){
            val = abs(a[i]-b[j]);
            ptr = j;
        }
    }
    sum += val;
}
return sum;

그러나 아래와 같이 이전에 작은 손해가 있지만 결과적으로 최소가 나오는 케이스에서 오답을 출력했다.
3 6
10 20 30
9 10 11 100 101 102

A = [10, 20, 30]
B = [ 9, 10, 11]
이여야하는데, 원소가 하나씩 밀려서 B = [10, 11, 100] 가 되버린 것이다.

이거 DP구나 깨닫고 $O(|A||B|)$ 를 다 해보자 싶었다.
int solve(int apos, int bpos){
    if(apos == n) return 0;
    
    int& r = dp[apos][bpos];
    if(r != -1) return r;
    
    r = INF;
    for(int i=bpos; i<=m-(n-apos); ++i){
        r = min(r, abs(a[apos]-b[i]) + solve(apos+1, i+1));
    }
    return r;
}

범위를 $|B|$ 배열 길이만큼이 아닌, 남은 $|A|$의 길이만큼으로 했지만 역시 느렸다. 속도가 빠른 소스들을 보니 이렇게 해결한 것 같다.
$min( b[bpos]$를 선택하는 경우, 선택하지 않고 $b[bpos+1]$과 $a[pos]$를 매칭하는 경우 $)$

재귀가 2번씩만 호출되니까 엄청 빨라졌다.

2016년 4월 21일 목요일

1509 - 팰린드롬 분할

https://www.acmicpc.net/problem/1509
$dp$[$i$] = $i$번째 위치에서 가능한 팰린드롬 분할의 최소 개수
$isPaline$[$i$][$j$] = $i$~$j$ 가 팰린드롬인가
예제에도 끼워져있지만 AABDBADD 와 같은 경우의 처리때문에 여러 경우를 탐색해야한다. 이것을 분할하는 경우는 아래와 같다.
A - A - B - D - B - A - D - D
A - A - B - D - B - A - DD
A - A - BDB - A - D - D
A - A - BDB - A - DD
A - ABDBA - D - D
A - ABDBA - DD
AA - B - D - B - A - D - D
AA - B - D - B - A - DD
AA - BDB - A - D - D
AA - BDB - A - DD
팰린드롬으로 최대한 묶은 후, 남은 문자열에 대해서 이 작업을 반복한다.

검색 기능 추가

왼쪽 상단에 검색이 이미 있었지만, 오른쪽 네비게이션에도 추가해봤다.

차이점은
왼쪽 상단 검색은 검색 결과와 매칭되는 포스팅이 전부 나오고
오른쪽 검색은 구글에서 검색된 결과와 연결되어 있는 것 같다. 제목와 간단한 내용들만 리스트 형태로 나온다.

2624 - 동전 바꿔주기

https://www.acmicpc.net/problem/2624

작은 돈에 대해서 만들어질 수 있는 가지 수를 구하고, 그 가지 수들에 또 다른 동전을 추가하면 얼마나 더 만들 수 있는지를 추가하였다.
$dp$[$N$][$K$] = $N$번째 동전까지로 $K$원을 만들 수 있는 가지 수
N번째 동전마다, 이전의 K원에다가 (N번째 동전의 개수)만큼 더해보면서 반복하면 된다.
예를 들어, 입력이 아래와 같다면
10
3
3 2
5 1
1 3
1원으로 0, 1, 2, 3원을 1개씩 만들 수 있을 것이다. (최대 3개 사용)
여기에 3원을 0개 추가하면 0+0, 1+0, 2+0, 3+0원을 만들 수 있고, 1개 추가하면 3, 4, 5, 6원을. 2개 추가하면 6, 7, 8, 9원을 만들 수 있다.
이런식으로 각 단위마다 가지 수를 누적하면서 살피면 dp[N][만들고자 하는 돈] 에 정답이 들어있을 것이다.

위 예시의 정답은 1 (1원 2개, 3원 1개, 5원 1개) 이다.

2016년 4월 20일 수요일

C/C++ 연산자 우선순위

우선순위연산자설명결합방향
1::Scope resolution (범위 확인)좌 → 우
2++ --후위 증가와 감소
()함수호출
[]배열 첨자
.참조로 요소 선택
->포인터를 통해 요소 선택
3++ --전위 증가와 감소우 → 좌
+ 단항 덧셈, 뺄셈
! ~논리 NOT, 비트단위 NOT
(type)타입 캐스트
*역참조
&주소값
sizeofSize-of 연산자
newnew[]동적 메모리 할당
deletedelete[]동적 메모리 해제
4.*
->*
멤버 접근좌 → 우
5* / %곱셈, 나눗셈, 나머지
6+ 더하기, 빼기
7<< >>비트 왼쪽 쉬프트와 오른쪽 쉬프트
8< <=관계 연산자 < 와 ≤
> >=관계 연산자 > 와 ≥
9== !=관계 = 와 ≠
10&비트 AND
11^비트 XOR (exclusive or)
12|비트 OR (inclusive or)
13&&논리 AND
14||논리 OR
15?:삼항연산자우 → 좌
=직접 할당 (C++ 클래스를 위해 기본 제공)
+= −=합과 차 할당
*= /= %=곱, 몫, 나머지 할당
<<= >>=비트 왼쪽 쉬프트와 오른쪽 쉬프트 후 할당
&= ^= |=비트연산 AND, XOR, OR 연산 후 할당
16throw(예외를 위한)Throw 연산자
17,콤마좌 → 우
숫자가 낮을수록 먼저 계산한다. (연산자 우선순위 표)

연산자 우선순위때문에 코딩미스가 자주 나타나곤 한다. 이제까지 코딩하면서 이런 우선순위때문에 ~얻거나 잃은~ 겪어본 것들을 적어보고자 한다.

1. &&|| 같은 경우에는 좌에서 우로 연산을 살피기 때문에 런타임 오류(divide by zero, out of range 등)을 예상하여 막을 수 있다.
 예를 들면, if(n >= 0 && arr[n] == 1) return 0; 과 같은 코드는 n이 음수일 때는 n >= 0 이 false 이므로 더 이상 조건을 확인하지 않는다. 즉, arr[음수] 인 경우가 없으므로 out of range는 일어나지 않는다. OR연산인 || 역시 중간에 true인 조건이 확인되면 마찬가지로 더 이상 확인하지 않는다.

2. << 과 같은 쉬프트 연산은 사칙연산보다 우선순위가 낮기 때문에 $2^n$ 꼴을 쉬프트로 표현하려면 괄호를 적절히 사용해야한다.
 printf("%d", 1<<2+1); 의 출력값은 5가 아닌 8이다.

3. printf는 우에서 좌로 해석한다. (단, C99에서. 이 링크에 의하면, C++에는 순서가 정의되어 있지 않고 컴파일러의 최적화에 따라 다르다고 한다.) 아래 코드의 출력값을 예상해보자.
main(){
    int a=1, b=8, c=4;
    printf("%d %d %d", ++a+c, b/a, b*c++);
}
결과는 6 4 32 가 아닌 7 8 32이다.
그 이유는 printf 함수는 가변 인자를 받아서 인자의 개수를 모르기 때문이다.

4. 타입 캐스팅은 자료형이 작은 쪽에서 큰 쪽으로 일어난다.
  • int + int = int
  • long long + int = long long
    • ex) long long r = 15 + INT_MAX 는 오버플로우지만, long long r = 15LL + INT_MAX 는 잘 들어간다.
  • int + double = double
    • ex) int를 double로 변환할 때 쓰면 편하다. (int)50+(double)0.0 은 (double)50.0로 변환된다.

5. 연산의 우선순위는 분명 존재한다. 하지만 잘 작동할 수 있다.
격자무늬를 표현하는 r%2^c%2 라는 코드는 XOR(^)의 우선순위가 mod(%)보다 낮음에도 (r%2)^(c%2) 처럼 잘 작동한다.
3*k++ 과 같이 모호한 코드는 작성하지 않는 것이 가장 좋은 방법이다.

마지막으로, 이 모든 내용은 컴파일러마다 다를 수 있다. 그리고 명시적인 표현의 여부에 따라 다를 수 있다.
작성된 코드가 의도와 같게 작동하지 않을 때, '이럴 수도 있다' 정도의 지식으로만 참고하면 좋을 것 같다.

잘못된 내용이 있다면 지적해주시면 감사하겠습니다.


5430 - AC

https://www.acmicpc.net/problem/5430

배열이 뒤집혀도 배열의 첫번째 숫자를 버립니다. 다시 말하면, 배열의 마지막 숫자를 버리는 것은 배열이 뒤집혔을때 버리는 것과 같다.

뒤집기 연산이 들어올때마다 "앞에서 버릴 지, 뒤에서 버릴 지"를 번갈아주도록 하면 된다.

pop_front 와 pop_back 을 위해 데크를 사용한다.

같이 보기

2016년 4월 13일 수요일

3077 - 임진왜란

https://www.acmicpc.net/problem/3077

주어진 사건들의 이름에 대응되는 인덱스들만 저장해서 나중에 $O(N^{2})$ 만큼 돌면서 크기가 $i$번째 이후 중에서 $i$번째보다 큰 인덱스만 세려고 했다.

자료구조 map을 써서 각 사건들의 인덱스를 저장한 후, 사건이 입력될 때마다 j=i+1~N 의 반복문에서 index[ 사건[i] ] < index[ 사건[j] ] 를 썼더니 map에서 조회 연산이 $O(NlogN)$ 이라 그런지 시간초과를 먹었다. (당시에는 map에서 조회하는 연산을 생각하지 못해서 30분 정도 삽질한듯.. 생각나는 대로 코딩하는 자의 최후)

11581 - 구호물자

https://www.acmicpc.net/problem/11581
2015 인하대학교 프로그래밍 경시대회 G번

처음에는 BFS를 돌면서 다시 방문하는 정점이 있는 경우 NO CYCLE을 출력하고 끝내려했는데, 코딩 미스인건지 오답을 맞았다. (이거 BFS로는 안되는건가요? 누가 댓글로 답변 바람) (참고로 다들 DFS로 푼 것 같다..)

딱히 좋은 아이디어는 안 떠올라서 플로이드 와샬로 정점과 정점이 이동 가능한지 저장했다. 그리고 1번 정점에서 $i$번 정점까지 방문 가능한 것들 중에 $i$번 정점에서 $i$번 정점으로 다시 돌아올 수 있는 (싸이클이 생기는) 경우가 있다면 CYCLE을 출력하도록 했다.

입출력 몇 개를 예로 적어놔야겠다.

예제 #1
6
2 2 3
2 4 6
1 4
1 5
1 3
CYCLE

예제 #2
6
2 2 6
1 6
1 4
2 2 5
1 3
NO CYCLE

2016년 4월 11일 월요일

10888 - 두 섬간의 이동

https://www.acmicpc.net/problem/10888

다리를 N-1개 지을때마다 그 때까지의 정부가 원하는 2개의 값 현황을 알려줘야한다. 정부가 원하는 값 2개는

  • 두 섬 간에 왕래가 가능한 섬들 (i,j) (i<j) 쌍들의 개수
  • 두 섬 (i,j)가 왕래 가능할 때 섬 i에서 섬 j까지 가기 위해 이용해야 하는 최소 다리 개수의 합

와 같다. 원하는 값을 설명하기 너무 기니까 왕래 가능한 쌍들의 개수를 pairs, 섬 들이 서로 이동하는데 필요한 최소 다리 개수의 합을 bridges라고 하겠다.

이 문제 역시 서로소 집합(Disjoint Set)으로 해결할 수 있다. pairs는 서로 연결된 섬들의 개수를 통해 알 수 있다.
서로 연결된 섬들의 개수를 $x$라 하면 $x=4$일 때 (1, 2) (1, 3) (1, 4) (2, 3) (2, 4) (3, 4) 로 6개이다.
i번째에서 i<j인 개수만큼 더하므로 $$ pairs = \sum_{k=1}^x k$$ 로 나타낼 수 있다.

그럼 bridges는 이러한 pairs개 만큼의 쌍들이 x-1개, x-2개, ..., 1개만큼 떨어져있으므로 전부 더한 값이다.
예를 들어 자세히 설명하자면, 섬 4개에 번호를 붙여서 1-2-3-4 라 하면, 1에서 나올 수 있는 쌍은 ~2, ~3, ~4까지이고 각각 최소 다리의 개수는 1, 2, 3 이므로 1에서 출발하는 경우는 1+2+3=6 이다. 2에서 출발하는 경우는 1+2 이고, 3에서 출발하는 경우는 1 (3-4. 1개)이다. 그럼 섬이 4개일 때 bridges는 1+(1+2)+(1+2+3)=10 이다.

수식으로는 $$\sum_{k=1}^{n-1} \left(\sum_{i=1}^k i\right) = \frac{(n-1)n(n+1)}{6}$$ 가 된다.

그리고 갈라져있던 두 집합이 합쳐지는 경우가 있는데, 이 때는 각 집합에서의 pairs와 bridges를 빼고 합친 후의 pairs와 bridges를 더해주면 전체 값은 유지되면서 갱신한 후에 센 값과 같다.

다음은 코딩할 때 사용한 샘플 입출력이다.

예제 입력
5
1 2 4 3

예제 출력
1 1
3 4
4 5
10 20

4195 - 친구 네트워크

https://www.acmicpc.net/problem/4195

네트워크의 구조와 상관없이 몇 명이 존재하는 지만 확인하면 되기 때문에 서로소 집합(Disjoint Set)으로 해결할 수 있다.

사용자의 아이디가 대소문자의 최대 20자라길래 $(26+26)^{20}$ 이면 long long으로 변환하면 편하겠거니 했는 데, 서로소 집합에서 번호를 나타내려면 이름마다 번호를 하나씩 붙이는 게 나을 것 같았다.

네트워크를 만들고 합치는 것은 간단하다. root배열을 각 원소들이 속해있는 집합의 번호라고 했을 때 p번과 q번을 합치려면 아래와 같다.
int root[100001];

int find(int k){
    if(k == root[k]) return k;
    return root[k] = find(root[k]);
}

void merge(int p, int q){
    p = find(p);
    q = find(q);
    if(p != q) root[q] = p;
}
네트워크에 속해있는 원소들의 수는 어떻게 세야할까. 위 코드대로라면 원소는 대충 아래와 같이 있을 것이다.

[그림 1]
화살표로 가리키는 것은 부모-자식의 관계가 아니라 집합의 번호이다. 위처럼 두 집합은 초록색 원소가 그 집합의 대표라고 할 수 있다. 그럼 집합의 개수는 초록색 원소 위에 있는 숫자라고 하자.

(q가 p에 흡수되는) 위 코드를 참고하여 두 집합을 합치면 [그림 2]와 같이 나타낼 수 있다.

[그림 2]
q의 개수를 p에 더해주고, q는 1개(나중에 다른 집합으로 옮겨질 것을 우려한다면)로 바꿔주면 된다.
나머지 원소들의 집합 번호를 바꾸지 않아도 되는 것은, 나중에 조회할 때 find(e) 를 해주면 다시 갱신되기 때문이다.

서로 다른 이름이 N개의 줄마다 2개씩 나올 수 있으므로 배열의 범위는 $2N$으로 설정했다.

2016년 4월 10일 일요일

Google code jam - Qualification Round 2016

https://code.google.com/codejam/contest/6254486/dashboard
Google code jam - Qualification Round 2016 풀이

Problem A - Counting Sheep

N이 주어지면 N, 2N, ..., kN까지 진행했을 때, 0~9을 모두 사용하게 되는 시점 k에서의 kN을 구하는 문제이다.

문제 그대로 시뮬레이션하면 된다. large set에서 범위가 $0 \le N \le 10^6$ 이지만 0~9가 모두 나오게 되는건 최대 100배이므로 int 범위로 충분히 표현할 수 있다.

Problem B - Revenge of the Pancakes

앞에서부터 k번째까지를 뒤집을 수 있다. (0~k 를 k~0으로 뒤집고 모든 -를 +로 뒤집는다. +-- 를 전부 뒤집는다면 ++- 이 된다.) 최소 몇 번을 이런 식으로 뒤집으면 +로만 이루어진 문자열을 만들 수 있을까?

small set은 재귀로 구현했으나 코딩이 미숙하여 계속 틀렸다. 각 문자열을 1, 2, ..., n번까지 뒤집으면서 BFS를 진행하였다. small set은 길이 $S \le 10$ 이므로 모든 경우의 수는 $2^S$ 라서 최대 1024개만 탐색할 것이다.

small set의 정답을 들여다보고 large set을 해결할 수 있었다. 다음 문자열들은 모두 같은 연산 횟수를 가진다.
++---
+++--
+--
+-
+++-
그러면 문제를 분할할 수 있다. 위 2개의 합은 세번째의 결과와 같다.
+++---
+--
+++---+--
연속되어 있는 문자열은 하나로 생각해도 무방하다. (뒤집는 과정을 생각하면 연속되어 있는 것은 한번에 뒤집는 것이 좋다는 걸 알 수 있다.) 그렇다면 -+ 또는 +- 들만 계산하면 된다.

- 로 시작하는 경우는 처음에 등장한 - 를 뒤집고 나머지를 계산하면 된다.

Problem C - Coin Jam (small)

2진수의 길이가 N인 1~~~1의 형태(~는 0 또는 1)를 가진 10진수를 2, 3, ..., 10진수로 해석한 모든 수가 소수가 아닌 것들을 J개 출력하는 문제이다.

small set은 단순 시뮬레이션으로 해결했다.

N=16, J=50인 경우의 결과를 보면 답이 3 2 5 2 7 2 3 2 11가 자주 등장하는 데, 각 진수들이 3 2 5 2 7 2 3 2 11로 나뉘는 경우들을 출력하면 large set(N=32, J=500)도 해결된다고 한다. (Big-Integer를 사용)

Problem D - Fractiles

문제 이해 못함

2016년 4월 6일 수요일

1613 - 역사

https://www.acmicpc.net/problem/1613

입력으로 주어지는 사건들의 관계를 방향그래프로 생각하면 사건 A와 사건 C 사이에 A->B->C 의 순서가 되는 사건 B가 있으면, 사건 C는 사건 A 이후에 발생했다는 사실을 알 수 있다.

플로이드 와샬을 통해 각 사건들의 관계를 모두 구하면 s줄에 걸친 물음에 대답할 수 있다.

9322 - 철벽 보안 알고리즘

https://www.acmicpc.net/problem/9322

제 1 공개키와 제 2 공개키를 비교하여 위치가 어떻게 바뀌는 지 저장하고 암호문은 그 반대로 위치를 참조하여 출력하도록 했다.

4900 - 7 더하기

https://www.acmicpc.net/problem/4900

코드로 된 입력을 3자리씩 끊어서 0~9 중 해당하는 숫자로 변환한 후 연산하고 다시 코드로 변환했다.

2660 - 회장 뽑기

https://www.acmicpc.net/problem/2660

친구의 친구 사이는 친구이므로, 각 사람들에 대해 서로 가장 가까운 정도를 구할 수 있다.

구해진 가까운 정도(최단 경로)들이 가장 작은 사람들의 리스트를 만들어 출력한다.

1977 - 완전제곱수

https://www.acmicpc.net/problem/1977

M 이상이 되는 제곱수를 찾은 후, N 이하가 될 때 까지 나오는 제곱수를 모두 더한다.

게시글 목록